/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DeferredCommand; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize; import com.vaadin.terminal.gwt.client.RenderInformation.Size; import com.vaadin.terminal.gwt.client.ui.Field; import com.vaadin.terminal.gwt.client.ui.VContextMenu; import com.vaadin.terminal.gwt.client.ui.VNotification; import com.vaadin.terminal.gwt.client.ui.VView; import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; /** * This is the client side communication "engine", managing client-server * communication with its server side counterpart * {@link AbstractCommunicationManager}. * * Client-side widgets receive updates from the corresponding server-side * components as calls to * {@link Paintable#updateFromUIDL(UIDL, ApplicationConnection)} (not to be * confused with the server side interface {@link com.vaadin.terminal.Paintable} * ). Any client-side changes (typically resulting from user actions) are sent * back to the server as variable changes (see {@link #updateVariable()}). * * TODO document better * * Entry point classes (widgetsets) define onModuleLoad(). */ public class ApplicationConnection { // This indicates the whole page is generated by us (not embedded) public static final String GENERATED_BODY_CLASSNAME = "v-generated-body"; private static final String MODIFIED_CLASSNAME = "v-modified"; private static final String REQUIRED_CLASSNAME_EXT = "-required"; private static final String ERROR_CLASSNAME_EXT = "-error"; public static final String VAR_RECORD_SEPARATOR = "\u001e"; public static final String VAR_FIELD_SEPARATOR = "\u001f"; public static final String VAR_BURST_SEPARATOR = "\u001d"; public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c"; public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key"; /** * @deprecated use UIDL_SECURITY_TOKEN_ID instead */ @Deprecated public static final String UIDL_SECURITY_HEADER = UIDL_SECURITY_TOKEN_ID; public static final String PARAM_UNLOADBURST = "onunloadburst"; public static final String ATTRIBUTE_DESCRIPTION = "description"; public static final String ATTRIBUTE_ERROR = "error"; // will hold the UIDL security key (for XSS protection) once received private String uidl_security_key = "init"; private final HashMap resourcesMap = new HashMap(); private static Console console; private final ArrayList pendingVariables = new ArrayList(); private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap .create(); private final WidgetSet widgetSet; private VContextMenu contextMenu = null; private Timer loadTimer; private Timer loadTimer2; private Timer loadTimer3; private Element loadElement; private final VView view; private boolean applicationRunning = false; private int activeRequests = 0; /** Parameters for this application connection loaded from the web-page */ private final ApplicationConfiguration configuration; /** List of pending variable change bursts that must be submitted in order */ private final ArrayList> pendingVariableBursts = new ArrayList>(); /** Timer for automatic refirect to SessionExpiredURL */ private Timer redirectTimer; /** redirectTimer scheduling interval in seconds */ private int sessionExpirationInterval; private ArrayList relativeSizeChanges = new ArrayList();; private ArrayList componentCaptionSizeChanges = new ArrayList();; private Date requestStartTime; private boolean validatingLayouts = false; private Set zeroWidthComponents = null; private Set zeroHeightComponents = null; public ApplicationConnection(WidgetSet widgetSet, ApplicationConfiguration cnf) { this.widgetSet = widgetSet; configuration = cnf; windowName = configuration.getInitialWindowName(); if (isDebugMode()) { console = new VDebugConsole(this, cnf, !isQuietDebugMode()); } else { console = new NullConsole(); } ComponentLocator componentLocator = new ComponentLocator(this); String appRootPanelName = cnf.getRootPanelId(); // remove the end (window name) of autogenerated rootpanel id appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", ""); initializeTestbenchHooks(componentLocator, appRootPanelName); initializeClientHooks(); view = new VView(cnf.getRootPanelId()); showLoadingIndicator(); } /** * Starts this application. Don't call this method directly - it's called by * {@link ApplicationConfiguration#startNextApplication()}, which should be * called once this application has started (first response received) or * failed to start. This ensures that the applications are started in order, * to avoid session-id problems. */ void start() { makeUidlRequest("", true, false, false); } private native void initializeTestbenchHooks( ComponentLocator componentLocator, String TTAppId) /*-{ var ap = this; var client = {}; client.isActive = function() { return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()() || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isLoadingIndicatorVisible()(); } var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()(); if (vi) { client.getVersionInfo = function() { return vi; } } client.getElementByPath = function(id) { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); } client.getPathForElement = function(element) { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); } if(!$wnd.vaadin.clients) { $wnd.vaadin.clients = {}; } $wnd.vaadin.clients[TTAppId] = client; }-*/; /** * Helper for tt initialization */ @SuppressWarnings("unused") private JavaScriptObject getVersionInfo() { return configuration.getVersionInfoJSObject(); } /** * Publishes a JavaScript API for mash-up applications. *
    *
  • vaadin.forceSync() sends pending variable changes, in * effect synchronizing the server and client state. This is done for all * applications on host page.
  • *
  • vaadin.postRequestHooks is a map of functions which gets * called after each XHR made by vaadin application. Note, that it is * attaching js functions responsibility to create the variable like this: * *
         * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
         * postRequestHooks.myHook = function(appId) {
         *          if(appId == "MyAppOfInterest") {
         *                  // do the staff you need on xhr activity
         *          }
         * }
         * 
    First parameter passed to these functions is the identifier * of Vaadin application that made the request. *
* * TODO make this multi-app aware */ private native void initializeClientHooks() /*-{ var app = this; var oldSync; if($wnd.vaadin.forceSync) { oldSync = $wnd.vaadin.forceSync; } $wnd.vaadin.forceSync = function() { if(oldSync) { oldSync(); } app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()(); } var oldForceLayout; if($wnd.vaadin.forceLayout) { oldForceLayout = $wnd.vaadin.forceLayout; } $wnd.vaadin.forceLayout = function() { if(oldForceLayout) { oldForceLayout(); } app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()(); } }-*/; /** * Runs possibly registered client side post request hooks. This is expected * to be run after each uidl request made by Vaadin application. * * @param appId */ private static native void runPostRequestHooks(String appId) /*-{ if($wnd.vaadin.postRequestHooks) { for(var hook in $wnd.vaadin.postRequestHooks) { if(typeof($wnd.vaadin.postRequestHooks[hook]) == "function") { try { $wnd.vaadin.postRequestHooks[hook](appId); } catch(e) {} } } } }-*/; /** * Get the active Console for writing debug messages. May return an actual * logging console, or the NullConsole if debugging is not turned on. * * @return the active Console */ public static Console getConsole() { return console; } /** * Checks if client side is in debug mode. Practically this is invoked by * adding ?debug parameter to URI. * * @return true if client side is currently been debugged */ public native static boolean isDebugMode() /*-{ if($wnd.vaadin.debug) { var parameters = $wnd.location.search; var re = /debug[^\/]*$/; return re.test(parameters); } else { return false; } }-*/; private native static boolean isQuietDebugMode() /*-{ var uri = $wnd.location; var re = /debug=q[^\/]*$/; return re.test(uri); }-*/; /** * Gets the application base URI. Using this other than as the download * action URI can cause problems in Portlet 2.0 deployments. * * @return application base URI */ public String getAppUri() { return configuration.getApplicationUri(); }; /** * Indicates whether or not there are currently active UIDL requests. Used * internally to squence requests properly, seldom needed in Widgets. * * @return true if there are active requests */ public boolean hasActiveRequest() { return (activeRequests > 0); } private void makeUidlRequest(final String requestData, final boolean repaintAll, final boolean forceSync, final boolean analyzeLayouts) { startRequest(); // Security: double cookie submission pattern final String rd = uidl_security_key + VAR_BURST_SEPARATOR + requestData; console.log("Making UIDL Request with params: " + rd); String uri; if (configuration.usePortletURLs()) { uri = configuration.getPortletUidlURLBase(); } else { uri = getAppUri() + "UIDL" + configuration.getPathInfo(); } if (repaintAll) { // collect some client side data that will be sent to server on // initial uidl request int clientHeight = Window.getClientHeight(); int clientWidth = Window.getClientWidth(); com.google.gwt.dom.client.Element pe = view.getElement() .getParentElement(); int offsetHeight = pe.getOffsetHeight(); int offsetWidth = pe.getOffsetWidth(); int screenWidth = BrowserInfo.get().getScreenWidth(); int screenHeight = BrowserInfo.get().getScreenHeight(); String token = History.getToken(); // TODO figure out how client and view size could be used better on // server. screen size can be accessed via Browser object, but other // values currently only via transaction listener. if (configuration.usePortletURLs()) { uri += "&"; } else { uri += "?"; } uri += "repaintAll=1&" + "sh=" + screenHeight + "&sw=" + screenWidth + "&cw=" + clientWidth + "&ch=" + clientHeight + "&vw=" + offsetWidth + "&vh=" + offsetHeight + "&fr=" + token; if (analyzeLayouts) { uri += "&analyzeLayouts=1"; } } if (windowName != null && windowName.length() > 0) { uri += (repaintAll || configuration.usePortletURLs() ? "&" : "?") + "windowName=" + windowName; } if (!forceSync) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); // TODO enable timeout // rb.setTimeoutMillis(timeoutMillis); rb.setHeader("Content-Type", "text/plain;charset=utf-8"); try { rb.sendRequest(rd, new RequestCallback() { public void onError(Request request, Throwable exception) { showCommunicationError(exception.getMessage()); endRequest(); if (!applicationRunning) { // start failed, let's try to start the next app ApplicationConfiguration.startNextApplication(); } } public void onResponseReceived(Request request, Response response) { console.log("Server visit took " + String.valueOf((new Date()).getTime() - requestStartTime.getTime()) + "ms"); int statusCode = response.getStatusCode(); if ((statusCode / 100) == 4) { // Handle all 4xx errors the same way as (they are // all permanent errors) showCommunicationError("UIDL could not be read from server. Check servlets mappings. Error code: " + statusCode); endRequest(); return; } switch (statusCode) { case 0: showCommunicationError("Invalid status code 0 (server down?)"); endRequest(); return; case 503: // We'll assume msec instead of the usual seconds int delay = Integer.parseInt(response .getHeader("Retry-After")); console.log("503, retrying in " + delay + "msec"); (new Timer() { @Override public void run() { activeRequests--; makeUidlRequest(requestData, repaintAll, forceSync, analyzeLayouts); } }).schedule(delay); return; } if (applicationRunning) { handleReceivedJSONMessage(response); } else { applicationRunning = true; handleWhenCSSLoaded(response); ApplicationConfiguration.startNextApplication(); } } int cssWaits = 0; static final int MAX_CSS_WAITS = 20; private void handleWhenCSSLoaded(final Response response) { int heightOfLoadElement = DOM.getElementPropertyInt( loadElement, "offsetHeight"); if (heightOfLoadElement == 0 && cssWaits < MAX_CSS_WAITS) { (new Timer() { @Override public void run() { handleWhenCSSLoaded(response); } }).schedule(50); console .log("Assuming CSS loading is not complete, " + "postponing render phase. " + "(.v-loading-indicator height == 0)"); cssWaits++; } else { handleReceivedJSONMessage(response); if (cssWaits >= MAX_CSS_WAITS) { console .error("CSS files may have not loaded properly."); } } } }); } catch (final RequestException e) { ClientExceptionHandler.displayError(e); endRequest(); } } else { // Synchronized call, discarded response (leaving the page) SynchronousXHR syncXHR = (SynchronousXHR) SynchronousXHR.create(); syncXHR.synchronousPost(uri + "&" + PARAM_UNLOADBURST + "=1", rd); } } /** * Shows the communication error notification. The 'details' only go to the * console for now. * * @param details * Optional details for debugging. */ private void showCommunicationError(String details) { console.error("Communication error: " + details); String html = ""; if (configuration.getCommunicationErrorCaption() != null) { html += "

" + configuration.getCommunicationErrorCaption() + "

"; } if (configuration.getCommunicationErrorMessage() != null) { html += "

" + configuration.getCommunicationErrorMessage() + "

"; } if (html.length() > 0) { VNotification n = new VNotification(1000 * 60 * 45); n.addEventListener(new NotificationRedirect(configuration .getCommunicationErrorUrl())); n .show(html, VNotification.CENTERED_TOP, VNotification.STYLE_SYSTEM); } else { redirect(configuration.getCommunicationErrorUrl()); } } private void startRequest() { activeRequests++; requestStartTime = new Date(); // show initial throbber if (loadTimer == null) { loadTimer = new Timer() { @Override public void run() { /* * IE7 does not properly cancel the event with * loadTimer.cancel() so we have to check that we really * should make it visible */ if (loadTimer != null) { showLoadingIndicator(); } } }; // First one kicks in at 300ms } loadTimer.schedule(300); } private void endRequest() { if (applicationRunning) { checkForPendingVariableBursts(); runPostRequestHooks(configuration.getRootPanelId()); } activeRequests--; // deferring to avoid flickering DeferredCommand.addCommand(new Command() { public void execute() { if (activeRequests == 0) { hideLoadingIndicator(); } } }); } /** * This method is called after applying uidl change set to application. * * It will clean current and queued variable change sets. And send next * change set if it exists. */ private void checkForPendingVariableBursts() { cleanVariableBurst(pendingVariables); if (pendingVariableBursts.size() > 0) { for (Iterator> iterator = pendingVariableBursts .iterator(); iterator.hasNext();) { cleanVariableBurst(iterator.next()); } ArrayList nextBurst = pendingVariableBursts.get(0); pendingVariableBursts.remove(0); buildAndSendVariableBurst(nextBurst, false); } } /** * Cleans given queue of variable changes of such changes that came from * components that do not exist anymore. * * @param variableBurst */ private void cleanVariableBurst(ArrayList variableBurst) { for (int i = 1; i < variableBurst.size(); i += 2) { String id = variableBurst.get(i); id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR)); if (!idToPaintableDetail.containsKey(id) && !id.startsWith("DD")) { // variable owner does not exist anymore variableBurst.remove(i - 1); variableBurst.remove(i - 1); i -= 2; ApplicationConnection.getConsole().log( "Removed variable from removed component: " + id); } } } private void showLoadingIndicator() { // show initial throbber if (loadElement == null) { loadElement = DOM.createDiv(); DOM.setStyleAttribute(loadElement, "position", "absolute"); DOM.appendChild(view.getElement(), loadElement); ApplicationConnection.getConsole().log("inserting load indicator"); } DOM.setElementProperty(loadElement, "className", "v-loading-indicator"); DOM.setStyleAttribute(loadElement, "display", "block"); // Initialize other timers loadTimer2 = new Timer() { @Override public void run() { DOM.setElementProperty(loadElement, "className", "v-loading-indicator-delay"); } }; // Second one kicks in at 1500ms from request start loadTimer2.schedule(1200); loadTimer3 = new Timer() { @Override public void run() { DOM.setElementProperty(loadElement, "className", "v-loading-indicator-wait"); } }; // Third one kicks in at 5000ms from request start loadTimer3.schedule(4700); } private void hideLoadingIndicator() { if (loadTimer != null) { loadTimer.cancel(); if (loadTimer2 != null) { loadTimer2.cancel(); loadTimer3.cancel(); } loadTimer = null; } if (loadElement != null) { DOM.setStyleAttribute(loadElement, "display", "none"); } } /** * Determines whether or not the loading indicator is showing. * * @return true if the loading indicator is visible */ public boolean isLoadingIndicatorVisible() { if (loadElement == null) { return false; } if (loadElement.getStyle().getProperty("display").equals("none")) { return false; } return true; } private static native ValueMap parseJSONResponse(String jsonText) /*-{ try { return JSON.parse(jsonText); } catch(ignored) { return eval('(' + jsonText + ')'); } }-*/; private void handleReceivedJSONMessage(Response response) { final Date start = new Date(); // for(;;);[realjson] final String jsonText = response.getText().substring(9, response.getText().length() - 1); final ValueMap json; try { json = parseJSONResponse(jsonText); } catch (final Exception e) { endRequest(); showCommunicationError(e.getMessage() + " - Original JSON-text:"); console.log(jsonText); return; } ApplicationConnection.getConsole().log( "JSON parsing took " + (new Date().getTime() - start.getTime()) + "ms"); // Handle redirect if (json.containsKey("redirect")) { String url = json.getValueMap("redirect").getString("url"); console.log("redirecting to " + url); redirect(url); return; } // Get security key if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) { uidl_security_key = json.getString(UIDL_SECURITY_TOKEN_ID); } if (json.containsKey("resources")) { ValueMap resources = json.getValueMap("resources"); JsArrayString keyArray = resources.getKeyArray(); int l = keyArray.length(); for (int i = 0; i < l; i++) { String key = keyArray.get(i); resourcesMap.put(key, resources.getAsString(key)); } } if (json.containsKey("typeMappings")) { configuration.addComponentMappings( json.getValueMap("typeMappings"), widgetSet); } Command c = new Command() { public void execute() { if (json.containsKey("locales")) { // Store locale data JsArray valueMapArray = json .getJSValueMapArray("locales"); LocaleService.addLocales(valueMapArray); } ValueMap meta = null; if (json.containsKey("meta")) { meta = json.getValueMap("meta"); if (meta.containsKey("repaintAll")) { view.clear(); idToPaintableDetail.clear(); if (meta.containsKey("invalidLayouts")) { validatingLayouts = true; zeroWidthComponents = new HashSet(); zeroHeightComponents = new HashSet(); } } if (meta.containsKey("timedRedirect")) { final ValueMap timedRedirect = meta .getValueMap("timedRedirect"); redirectTimer = new Timer() { @Override public void run() { redirect(timedRedirect.getString("url")); } }; sessionExpirationInterval = timedRedirect .getInt("interval"); } } if (redirectTimer != null) { redirectTimer.schedule(1000 * sessionExpirationInterval); } // Process changes JsArray changes = json.getJSValueMapArray("changes"); ArrayList updatedWidgets = new ArrayList(); relativeSizeChanges.clear(); componentCaptionSizeChanges.clear(); int length = changes.length(); for (int i = 0; i < length; i++) { try { final UIDL change = changes.get(i).cast(); try { console.dirUIDL(change); } catch (final Exception e) { ClientExceptionHandler.displayError(e); // TODO: dir doesn't work in any browser although it // should // work (works in hosted mode) // it partially did at some part but now broken. } final UIDL uidl = change.getChildUIDL(0); // TODO optimize final Paintable paintable = getPaintable(uidl.getId()); if (paintable != null) { paintable.updateFromUIDL(uidl, ApplicationConnection.this); // paintable may have changed during render to // another // implementation, use the new one for updated // widgets map updatedWidgets.add(idToPaintableDetail.get( uidl.getId()).getComponent()); } else { if (!uidl.getTag().equals( configuration.getEncodedWindowTag())) { ClientExceptionHandler .displayError("Received update for " + uidl.getTag() + ", but there is no such paintable (" + uidl.getId() + ") rendered."); } else { view.updateFromUIDL(uidl, ApplicationConnection.this); } } } catch (final Throwable e) { ClientExceptionHandler.displayError(e); } } if (json.containsKey("dd")) { // response contains data for drag and drop service VDragAndDropManager.get().handleServerResponse( json.getValueMap("dd")); } // Check which widgets' size has been updated Set sizeUpdatedWidgets = new HashSet(); updatedWidgets.addAll(relativeSizeChanges); sizeUpdatedWidgets.addAll(componentCaptionSizeChanges); for (Paintable paintable : updatedWidgets) { ComponentDetail detail = idToPaintableDetail .get(getPid(paintable)); Widget widget = (Widget) paintable; Size oldSize = detail.getOffsetSize(); Size newSize = new Size(widget.getOffsetWidth(), widget .getOffsetHeight()); if (oldSize == null || !oldSize.equals(newSize)) { sizeUpdatedWidgets.add(paintable); detail.setOffsetSize(newSize); } } Util.componentSizeUpdated(sizeUpdatedWidgets); if (meta != null) { if (meta.containsKey("appError")) { ValueMap error = meta.getValueMap("appError"); String html = ""; if (error.containsKey("caption") && error.getString("caption") != null) { html += "

" + error.getAsString("caption") + "

"; } if (error.containsKey("message") && error.getString("message") != null) { html += "

" + error.getAsString("message") + "

"; } String url = null; if (error.containsKey("url")) { url = error.getString("url"); } if (html.length() != 0) { /* 45 min */ VNotification n = new VNotification(1000 * 60 * 45); n.addEventListener(new NotificationRedirect(url)); n.show(html, VNotification.CENTERED_TOP, VNotification.STYLE_SYSTEM); } else { redirect(url); } applicationRunning = false; } if (validatingLayouts) { getConsole().printLayoutProblems(meta, ApplicationConnection.this, zeroHeightComponents, zeroWidthComponents); zeroHeightComponents = null; zeroWidthComponents = null; validatingLayouts = false; } } // TODO build profiling for widget impl loading time final long prosessingTime = (new Date().getTime()) - start.getTime(); console.log(" Processing time was " + String.valueOf(prosessingTime) + "ms for " + jsonText.length() + " characters of JSON"); console.log("Referenced paintables: " + idToPaintableDetail.size()); endRequest(); } }; configuration.runWhenWidgetsLoaded(c); } /** * This method assures that all pending variable changes are sent to server. * Method uses synchronized xmlhttprequest and does not return before the * changes are sent. No UIDL updates are processed and thus UI is left in * inconsistent state. This method should be called only when closing * windows - normally sendPendingVariableChanges() should be used. */ public void sendPendingVariableChangesSync() { if (applicationRunning) { pendingVariableBursts.add(pendingVariables); ArrayList nextBurst = pendingVariableBursts.get(0); pendingVariableBursts.remove(0); buildAndSendVariableBurst(nextBurst, true); } } // Redirect browser, null reloads current page private static native void redirect(String url) /*-{ if (url) { $wnd.location = url; } else { $wnd.location.reload(false); } }-*/; public void registerPaintable(String pid, Paintable paintable) { ComponentDetail componentDetail = new ComponentDetail(this, pid, paintable); idToPaintableDetail.put(pid, componentDetail); setPid(((Widget) paintable).getElement(), pid); } private native void setPid(Element el, String pid) /*-{ el.tkPid = pid; }-*/; /** * Gets the paintableId for a specific paintable (a.k.a Vaadin Widget). *

* The paintableId is used in the UIDL to identify a specific widget * instance, effectively linking the widget with it's server side Component. *

* * @param paintable * the paintable who's id is needed * @return the id for the given paintable */ public String getPid(Paintable paintable) { return getPid(((Widget) paintable).getElement()); } /** * Gets the paintableId using a DOM element - the element should be the main * element for a paintable otherwise no id will be found. Use * {@link #getPid(Paintable)} instead whenever possible. * * @see #getPid(Paintable) * @param el * element of the paintable whose pid is desired * @return the pid of the element's paintable, if it's a paintable */ public native String getPid(Element el) /*-{ return el.tkPid; }-*/; /** * Gets the main element for the paintable with the given id. The revers of * {@link #getPid(Element)}. * * @param pid * the pid of the widget whose element is desired * @return the element for the paintable corresponding to the pid */ public Element getElementByPid(String pid) { return ((Widget) getPaintable(pid)).getElement(); } /** * Unregisters the given paintable; always use after removing a paintable. * Does not actually remove the paintable from the DOM. * * @param p * the paintable to remove */ public void unregisterPaintable(Paintable p) { if (p == null) { ApplicationConnection.getConsole().error( "WARN: Trying to unregister null paintable"); return; } String id = getPid(p); idToPaintableDetail.remove(id); if (p instanceof HasWidgets) { unregisterChildPaintables((HasWidgets) p); } } /** * Unregisters a paintable and all it's child paintables recursively. Use * when after removing a paintable that contains other paintables. Does not * unregister the given container itself. Does not actually remove the * paintable from the DOM. * * @see #unregisterPaintable(Paintable) * @param container */ public void unregisterChildPaintables(HasWidgets container) { final Iterator it = container.iterator(); while (it.hasNext()) { final Widget w = it.next(); if (w instanceof Paintable) { unregisterPaintable((Paintable) w); } else if (w instanceof HasWidgets) { unregisterChildPaintables((HasWidgets) w); } } } /** * Returns Paintable element by its id * * @param id * Paintable ID */ public Paintable getPaintable(String id) { ComponentDetail componentDetail = idToPaintableDetail.get(id); if (componentDetail == null) { return null; } else { return componentDetail.getComponent(); } } private void addVariableToQueue(String paintableId, String variableName, String encodedValue, boolean immediate, char type) { final String id = paintableId + VAR_FIELD_SEPARATOR + variableName + VAR_FIELD_SEPARATOR + type; for (int i = 1; i < pendingVariables.size(); i += 2) { if ((pendingVariables.get(i)).equals(id)) { pendingVariables.remove(i - 1); pendingVariables.remove(i - 1); break; } } pendingVariables.add(encodedValue); pendingVariables.add(id); if (immediate) { sendPendingVariableChanges(); } } /** * This method sends currently queued variable changes to server. It is * called when immediate variable update must happen. * * To ensure correct order for variable changes (due servers multithreading * or network), we always wait for active request to be handler before * sending a new one. If there is an active request, we will put varible * "burst" to queue that will be purged after current request is handled. * */ @SuppressWarnings("unchecked") public void sendPendingVariableChanges() { if (applicationRunning) { if (hasActiveRequest()) { // skip empty queues if there are pending bursts to be sent if (pendingVariables.size() > 0 || pendingVariableBursts.size() == 0) { ArrayList burst = (ArrayList) pendingVariables .clone(); pendingVariableBursts.add(burst); pendingVariables.clear(); } } else { buildAndSendVariableBurst(pendingVariables, false); } } } /** * Build the variable burst and send it to server. * * When sync is forced, we also force sending of all pending variable-bursts * at the same time. This is ok as we can assume that DOM will never be * updated after this. * * @param pendingVariables * Vector of variable changes to send * @param forceSync * Should we use synchronous request? */ private void buildAndSendVariableBurst(ArrayList pendingVariables, boolean forceSync) { final StringBuffer req = new StringBuffer(); while (!pendingVariables.isEmpty()) { for (int i = 0; i < pendingVariables.size(); i++) { if (i > 0) { if (i % 2 == 0) { req.append(VAR_RECORD_SEPARATOR); } else { req.append(VAR_FIELD_SEPARATOR); } } req.append(pendingVariables.get(i)); } pendingVariables.clear(); // Append all the busts to this synchronous request if (forceSync && !pendingVariableBursts.isEmpty()) { pendingVariables = pendingVariableBursts.get(0); pendingVariableBursts.remove(0); req.append(VAR_BURST_SEPARATOR); } } makeUidlRequest(req.toString(), false, forceSync, false); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, Paintable newValue, boolean immediate) { String pid = (newValue != null) ? getPid(newValue) : null; addVariableToQueue(paintableId, variableName, pid, immediate, 'p'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, String newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue, immediate, 's'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, int newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, "" + newValue, immediate, 'i'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, long newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, "" + newValue, immediate, 'l'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, float newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, "" + newValue, immediate, 'f'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, double newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, "" + newValue, immediate, 'd'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, boolean newValue, boolean immediate) { addVariableToQueue(paintableId, variableName, newValue ? "true" : "false", immediate, 'b'); } /** * Sends a new value for the given paintables given variable to the server. *

* The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. *

* * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, Map map, boolean immediate) { final StringBuffer buf = new StringBuffer(); Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); Object value = map.get(key); char transportType = getTransportType(value); buf.append(transportType); buf.append(key); buf.append(VAR_ARRAYITEM_SEPARATOR); if (transportType == 'p') { buf.append(getPid((Paintable) value)); } else { buf.append(value); } if (iterator.hasNext()) { buf.append(VAR_ARRAYITEM_SEPARATOR); } } addVariableToQueue(paintableId, variableName, buf.toString(), immediate, 'm'); } private char getTransportType(Object value) { if (value instanceof String) { return 's'; } else if (value instanceof Paintable) { return 'p'; } else if (value instanceof Boolean) { return 'b'; } else if (value instanceof Integer) { return 'i'; } else if (value instanceof Float) { return 'f'; } else if (value instanceof Double) { return 'd'; } else if (value instanceof Long) { return 'l'; } else if (value instanceof Enum) { return 's'; // transported as string representation } return 'u'; } /** * Sends a new value for the given paintables given variable to the server. * * The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update. * * A null array is sent as an empty array. * * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, String[] values, boolean immediate) { final StringBuffer buf = new StringBuffer(); if (values != null) { for (int i = 0; i < values.length; i++) { buf.append(values[i]); // there will be an extra separator at the end to differentiate // between an empty array and one containing an empty string // only buf.append(VAR_ARRAYITEM_SEPARATOR); } } addVariableToQueue(paintableId, variableName, buf.toString(), immediate, 'c'); } /** * Sends a new value for the given paintables given variable to the server. * * The update is actually queued to be sent at a suitable time. If immediate * is true, the update is sent as soon as possible. If immediate is false, * the update will be sent along with the next immediate update.

* * A null array is sent as an empty array. * * * @param paintableId * the id of the paintable that owns the variable * @param variableName * the name of the variable * @param newValue * the new value to be sent * @param immediate * true if the update is to be sent as soon as possible */ public void updateVariable(String paintableId, String variableName, Object[] values, boolean immediate) { final StringBuffer buf = new StringBuffer(); if (values != null) { for (int i = 0; i < values.length; i++) { if (i > 0) { buf.append(VAR_ARRAYITEM_SEPARATOR); } Object value = values[i]; char transportType = getTransportType(value); // first char tells the type in array buf.append(transportType); if (transportType == 'p') { buf.append(getPid((Paintable) value)); } else { buf.append(value); } } } addVariableToQueue(paintableId, variableName, buf.toString(), immediate, 'a'); } /** * Update generic component features. * *

Selecting correct implementation

* *

* The implementation of a component depends on many properties, including * styles, component features, etc. Sometimes the user changes those * properties after the component has been created. Calling this method in * the beginning of your updateFromUIDL -method automatically replaces your * component with more appropriate if the requested implementation changes. *

* *

Caption, icon, error messages and description

* *

* Component can delegate management of caption, icon, error messages and * description to parent layout. This is optional an should be decided by * component author *

* *

Component visibility and disabling

* * This method will manage component visibility automatically and if * component is an instanceof FocusWidget, also handle component disabling * when needed. * * @param component * Widget to be updated, expected to implement an instance of * Paintable * @param uidl * UIDL to be painted * @param manageCaption * True if you want to delegate caption, icon, description and * error message management to parent. * * @return Returns true iff no further painting is needed by caller */ public boolean updateComponent(Widget component, UIDL uidl, boolean manageCaption) { String pid = getPid(component.getElement()); if (pid == null) { getConsole().error( "Trying to update an unregistered component: " + Util.getSimpleName(component)); return true; } ComponentDetail componentDetail = idToPaintableDetail.get(pid); if (componentDetail == null) { getConsole().error( "ComponentDetail not found for " + Util.getSimpleName(component) + " with PID " + pid + ". This should not happen."); return true; } // If the server request that a cached instance should be used, do // nothing if (uidl.getBooleanAttribute("cached")) { return true; } // register the listened events by the server-side to the event-handler // of the component componentDetail.registerEventListenersFromUIDL(uidl); // Visibility boolean visible = !uidl.getBooleanAttribute("invisible"); boolean wasVisible = component.isVisible(); component.setVisible(visible); if (wasVisible != visible) { // Changed invisibile <-> visible if (wasVisible && manageCaption) { // Must hide caption when component is hidden final Container parent = Util.getLayout(component); if (parent != null) { parent.updateCaption((Paintable) component, uidl); } } } if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) { DOM.setElementProperty(component.getElement(), "id", uidl.getId() .substring(5)); } if (!visible) { // component is invisible, delete old size to notify parent, if // later make visible componentDetail.setOffsetSize(null); return true; } // Switch to correct implementation if needed if (!widgetSet.isCorrectImplementation(component, uidl, configuration)) { final Container parent = Util.getLayout(component); if (parent != null) { final Widget w = (Widget) widgetSet.createWidget(uidl, configuration); parent.replaceChildComponent(component, w); unregisterPaintable((Paintable) component); registerPaintable(uidl.getId(), (Paintable) w); ((Paintable) w).updateFromUIDL(uidl, this); return true; } } boolean enabled = !uidl.getBooleanAttribute("disabled"); if (component instanceof FocusWidget) { FocusWidget fw = (FocusWidget) component; if (uidl.hasAttribute("tabindex")) { fw.setTabIndex(uidl.getIntAttribute("tabindex")); } // Disabled state may affect tabindex fw.setEnabled(enabled); } StringBuffer styleBuf = new StringBuffer(); final String primaryName = component.getStylePrimaryName(); styleBuf.append(primaryName); // first disabling and read-only status if (!enabled) { styleBuf.append(" "); styleBuf.append("v-disabled"); } if (uidl.getBooleanAttribute("readonly")) { styleBuf.append(" "); styleBuf.append("v-readonly"); } // add additional styles as css classes, prefixed with component default // stylename if (uidl.hasAttribute("style")) { final String[] styles = uidl.getStringAttribute("style").split(" "); for (int i = 0; i < styles.length; i++) { styleBuf.append(" "); styleBuf.append(primaryName); styleBuf.append("-"); styleBuf.append(styles[i]); styleBuf.append(" "); styleBuf.append(styles[i]); } } // add modified classname to Fields if (uidl.hasAttribute("modified") && component instanceof Field) { styleBuf.append(" "); styleBuf.append(MODIFIED_CLASSNAME); } TooltipInfo tooltipInfo = componentDetail.getTooltipInfo(null); // Update tooltip if (uidl.hasAttribute(ATTRIBUTE_DESCRIPTION)) { tooltipInfo .setTitle(uidl.getStringAttribute(ATTRIBUTE_DESCRIPTION)); } else { tooltipInfo.setTitle(null); } // add error classname to components w/ error if (uidl.hasAttribute(ATTRIBUTE_ERROR)) { tooltipInfo.setErrorUidl(uidl.getErrors()); styleBuf.append(" "); styleBuf.append(primaryName); styleBuf.append(ERROR_CLASSNAME_EXT); } else { tooltipInfo.setErrorUidl(null); } // add required style to required components if (uidl.hasAttribute("required")) { styleBuf.append(" "); styleBuf.append(primaryName); styleBuf.append(REQUIRED_CLASSNAME_EXT); } // Styles + disabled & readonly component.setStyleName(styleBuf.toString()); // Set captions if (manageCaption) { final Container parent = Util.getLayout(component); if (parent != null) { parent.updateCaption((Paintable) component, uidl); } } /* * updateComponentSize need to be after caption update so caption can be * taken into account */ updateComponentSize(componentDetail, uidl); return false; } private void updateComponentSize(ComponentDetail cd, UIDL uidl) { String w = uidl.hasAttribute("width") ? uidl .getStringAttribute("width") : ""; String h = uidl.hasAttribute("height") ? uidl .getStringAttribute("height") : ""; float relativeWidth = Util.parseRelativeSize(w); float relativeHeight = Util.parseRelativeSize(h); // First update maps so they are correct in the setHeight/setWidth calls if (relativeHeight >= 0.0 || relativeWidth >= 0.0) { // One or both is relative FloatSize relativeSize = new FloatSize(relativeWidth, relativeHeight); if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) { // The component has changed from absolute size to relative size relativeSizeChanges.add(cd.getComponent()); } cd.setRelativeSize(relativeSize); } else if (relativeHeight < 0.0 && relativeWidth < 0.0) { if (cd.getRelativeSize() != null) { // The component has changed from relative size to absolute size relativeSizeChanges.add(cd.getComponent()); } cd.setRelativeSize(null); } Widget component = (Widget) cd.getComponent(); // Set absolute sizes if (relativeHeight < 0.0) { component.setHeight(h); } if (relativeWidth < 0.0) { component.setWidth(w); } // Set relative sizes if (relativeHeight >= 0.0 || relativeWidth >= 0.0) { // One or both is relative handleComponentRelativeSize(cd); } } /** * Traverses recursively child widgets until ContainerResizedListener child * widget is found. They will delegate it further if needed. * * @param container */ private boolean runningLayout = false; /** * Causes a re-calculation/re-layout of all paintables in a container. * * @param container */ public void runDescendentsLayout(HasWidgets container) { if (runningLayout) { return; } runningLayout = true; internalRunDescendentsLayout(container); runningLayout = false; } /** * This will cause re-layouting of all components. Mainly used for * development. Published to JavaScript. */ public void forceLayout() { Set set = new HashSet(); for (ComponentDetail cd : idToPaintableDetail.values()) { set.add(cd.getComponent()); } Util.componentSizeUpdated(set); } private void internalRunDescendentsLayout(HasWidgets container) { // getConsole().log( // "runDescendentsLayout(" + Util.getSimpleName(container) + ")"); final Iterator childWidgets = container.iterator(); while (childWidgets.hasNext()) { final Widget child = childWidgets.next(); if (child instanceof Paintable) { if (handleComponentRelativeSize(child)) { /* * Only need to propagate event if "child" has a relative * size */ if (child instanceof ContainerResizedListener) { ((ContainerResizedListener) child).iLayout(); } if (child instanceof HasWidgets) { final HasWidgets childContainer = (HasWidgets) child; internalRunDescendentsLayout(childContainer); } } } else if (child instanceof HasWidgets) { // propagate over non Paintable HasWidgets internalRunDescendentsLayout((HasWidgets) child); } } } /** * Converts relative sizes into pixel sizes. * * @param child * @return true if the child has a relative size */ private boolean handleComponentRelativeSize(ComponentDetail cd) { if (cd == null) { return false; } boolean debugSizes = false; FloatSize relativeSize = cd.getRelativeSize(); if (relativeSize == null) { return false; } Widget widget = (Widget) cd.getComponent(); boolean horizontalScrollBar = false; boolean verticalScrollBar = false; Container parent = Util.getLayout(widget); RenderSpace renderSpace; // Parent-less components (like sub-windows) are relative to browser // window. if (parent == null) { renderSpace = new RenderSpace(Window.getClientWidth(), Window .getClientHeight()); } else { renderSpace = parent.getAllocatedSpace(widget); } if (relativeSize.getHeight() >= 0) { if (renderSpace != null) { if (renderSpace.getScrollbarSize() > 0) { if (relativeSize.getWidth() > 100) { horizontalScrollBar = true; } else if (relativeSize.getWidth() < 0 && renderSpace.getWidth() > 0) { int offsetWidth = widget.getOffsetWidth(); int width = renderSpace.getWidth(); if (offsetWidth > width) { horizontalScrollBar = true; } } } int height = renderSpace.getHeight(); if (horizontalScrollBar) { height -= renderSpace.getScrollbarSize(); } if (validatingLayouts && height <= 0) { zeroHeightComponents.add(cd.getComponent()); } height = (int) (height * relativeSize.getHeight() / 100.0); if (height < 0) { height = 0; } if (debugSizes) { getConsole() .log( "Widget " + Util.getSimpleName(widget) + "/" + getPid(widget.getElement()) + " relative height " + relativeSize.getHeight() + "% of " + renderSpace.getHeight() + "px (reported by " + Util.getSimpleName(parent) + "/" + (parent == null ? "?" : parent .hashCode()) + ") : " + height + "px"); } widget.setHeight(height + "px"); } else { widget.setHeight(relativeSize.getHeight() + "%"); ApplicationConnection.getConsole().error( Util.getLayout(widget).getClass().getName() + " did not produce allocatedSpace for " + widget.getClass().getName()); } } if (relativeSize.getWidth() >= 0) { if (renderSpace != null) { int width = renderSpace.getWidth(); if (renderSpace.getScrollbarSize() > 0) { if (relativeSize.getHeight() > 100) { verticalScrollBar = true; } else if (relativeSize.getHeight() < 0 && renderSpace.getHeight() > 0 && widget.getOffsetHeight() > renderSpace .getHeight()) { verticalScrollBar = true; } } if (verticalScrollBar) { width -= renderSpace.getScrollbarSize(); } if (validatingLayouts && width <= 0) { zeroWidthComponents.add(cd.getComponent()); } width = (int) (width * relativeSize.getWidth() / 100.0); if (width < 0) { width = 0; } if (debugSizes) { getConsole().log( "Widget " + Util.getSimpleName(widget) + "/" + getPid(widget.getElement()) + " relative width " + relativeSize.getWidth() + "% of " + renderSpace.getWidth() + "px (reported by " + Util.getSimpleName(parent) + "/" + (parent == null ? "?" : getPid(parent)) + ") : " + width + "px"); } widget.setWidth(width + "px"); } else { widget.setWidth(relativeSize.getWidth() + "%"); ApplicationConnection.getConsole().error( Util.getLayout(widget).getClass().getName() + " did not produce allocatedSpace for " + widget.getClass().getName()); } } return true; } /** * Converts relative sizes into pixel sizes. * * @param child * @return true if the child has a relative size */ public boolean handleComponentRelativeSize(Widget child) { return handleComponentRelativeSize(idToPaintableDetail.get(getPid(child .getElement()))); } /** * Gets the specified Paintables relative size (percent). * * @param widget * the paintable whose size is needed * @return the the size if the paintable is relatively sized, -1 otherwise */ public FloatSize getRelativeSize(Widget widget) { return idToPaintableDetail.get(getPid(widget.getElement())) .getRelativeSize(); } /** * Get either existing or new Paintable for given UIDL. * * If corresponding Paintable has been previously painted, return it. * Otherwise create and register a new Paintable from UIDL. Caller must * update the returned Paintable from UIDL after it has been connected to * parent. * * @param uidl * UIDL to create Paintable from. * @return Either existing or new Paintable corresponding to UIDL. */ public Paintable getPaintable(UIDL uidl) { final String id = uidl.getId(); Paintable w = getPaintable(id); if (w != null) { return w; } else { w = widgetSet.createWidget(uidl, configuration); registerPaintable(id, w); return w; } } /** * Returns a Paintable element by its root element * * @param element * Root element of the paintable */ public Paintable getPaintable(Element element) { return getPaintable(getPid(element)); } /** * Gets a recource that has been pre-loaded via UIDL, such as custom * layouts. * * @param name * identifier of the resource to get * @return the resource */ public String getResource(String name) { return resourcesMap.get(name); } /** * Singleton method to get instance of app's context menu. * * @return VContextMenu object */ public VContextMenu getContextMenu() { if (contextMenu == null) { contextMenu = new VContextMenu(); DOM.setElementProperty(contextMenu.getElement(), "id", "PID_VAADIN_CM"); } return contextMenu; } /** * Translates custom protocols in UIDL URI's to be recognizable by browser. * All uri's from UIDL should be routed via this method before giving them * to browser due URI's in UIDL may contain custom protocols like theme://. * * @param uidlUri * Vaadin URI from uidl * @return translated URI ready for browser */ public String translateVaadinUri(String uidlUri) { if (uidlUri == null) { return null; } if (uidlUri.startsWith("theme://")) { final String themeUri = configuration.getThemeUri(); if (themeUri == null) { console .error("Theme not set: ThemeResource will not be found. (" + uidlUri + ")"); } uidlUri = themeUri + uidlUri.substring(7); } return uidlUri; } /** * Gets the URI for the current theme. Can be used to reference theme * resources. * * @return URI to the current theme */ public String getThemeUri() { return configuration.getThemeUri(); } /** * Listens for Notification hide event, and redirects. Used for system * messages, such as session expired. * */ private class NotificationRedirect implements VNotification.EventListener { String url; NotificationRedirect(String url) { this.url = url; } public void notificationHidden(HideEvent event) { redirect(url); } } /* Extended title handling */ /** * Data showed in tooltips are stored centrilized as it may be needed in * varios place: caption, layouts, and in owner components themselves. * * Updating TooltipInfo is done in updateComponent method. * */ public TooltipInfo getTooltipTitleInfo(Paintable titleOwner, Object key) { if (null == titleOwner) { return null; } ComponentDetail cd = idToPaintableDetail.get(getPid(titleOwner)); if (null != cd) { return cd.getTooltipInfo(key); } else { return null; } } private final VTooltip tooltip = new VTooltip(this); /** * Component may want to delegate Tooltip handling to client. Layouts add * Tooltip (description, errors) to caption, but some components may want * them to appear one other elements too. * * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS * * @param event * @param owner */ public void handleTooltipEvent(Event event, Paintable owner) { tooltip.handleTooltipEvent(event, owner, null); } /** * Component may want to delegate Tooltip handling to client. Layouts add * Tooltip (description, errors) to caption, but some components may want * them to appear one other elements too. * * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS * * @param event * @param owner * @param key * the key for tooltip if this is "additional" tooltip, null for * components "main tooltip" */ public void handleTooltipEvent(Event event, Paintable owner, Object key) { tooltip.handleTooltipEvent(event, owner, key); } /** * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element. * * @param el * the IMG element to fix */ public void addPngFix(Element el) { BrowserInfo b = BrowserInfo.get(); if (b.isIE6()) { Util.addPngFix(el, getThemeUri() + "/../runo/common/img/blank.gif"); } } /* * Helper to run layout functions triggered by child components with a * decent interval. */ private final Timer layoutTimer = new Timer() { private boolean isPending = false; @Override public void schedule(int delayMillis) { if (!isPending) { super.schedule(delayMillis); isPending = true; } } @Override public void run() { getConsole().log( "Running re-layout of " + view.getClass().getName()); runDescendentsLayout(view); isPending = false; } }; /** * Components can call this function to run all layout functions. This is * usually done, when component knows that its size has changed. */ public void requestLayoutPhase() { layoutTimer.schedule(500); } private String windowName = null; /** * Reset the name of the current browser-window. This should reflect the * window-name used in the server, but might be different from the * window-object target-name on client. * * @param stringAttribute * New name for the window. */ public void setWindowName(String newName) { windowName = newName; } /** * Use to notify that the given component's caption has changed; layouts may * have to be recalculated. * * @param component * the Paintable whose caption has changed */ public void captionSizeUpdated(Paintable component) { componentCaptionSizeChanges.add(component); } /** * Requests an analyze of layouts, to find inconsistensies. Exclusively used * for debugging during develpoment. */ public void analyzeLayouts() { makeUidlRequest("", true, false, true); } /** * Gets the main view, a.k.a top-level window. * * @return the main view */ public VView getView() { return view; } /** * If component has several tooltips in addition to the one provided by * {@link com.vaadin.ui.AbstractComponent}, component can register them with * this method. *

* Component must also pipe events to * {@link #handleTooltipEvent(Event, Paintable, Object)} method. *

* This method can also be used to deregister tooltips by using null as * tooltip * * @param paintable * Paintable "owning" this tooltip * @param key * key assosiated with given tooltip. Can be any object. For * example a related dom element. Same key must be given for * {@link #handleTooltipEvent(Event, Paintable, Object)} method. * * @param tooltip * the TooltipInfo object containing details shown in tooltip, * null if deregistering tooltip */ public void registerTooltip(Paintable paintable, Object key, TooltipInfo tooltip) { ComponentDetail componentDetail = idToPaintableDetail .get(getPid(paintable)); componentDetail.putAdditionalTooltip(key, tooltip); } /** * Gets the {@link ApplicationConfiguration} for the current application. * * @see ApplicationConfiguration * @return the configuration for this application */ public ApplicationConfiguration getConfiguration() { return configuration; } /** * Checks if there is a registered server side listener for the event. The * list of events which has server side listeners is updated automatically * before the component is updated so the value is correct if called from * updatedFromUIDL. * * @param eventIdentifier * The identifier for the event * @return true if at least one listener has been registered on server side * for the event identified by eventIdentifier. */ public boolean hasEventListeners(Paintable paintable, String eventIdentifier) { return idToPaintableDetail.get(getPid(paintable)).hasEventListeners( eventIdentifier); } }