From: Matti Tahvonen Date: Tue, 13 Oct 2009 07:42:04 +0000 (+0000) Subject: IE6/7 fix X-Git-Tag: 6.7.0.beta1~2412 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6b733e8fdeef21430481aa8f3e375b116cd426dc;p=vaadin-framework.git IE6/7 fix svn changeset:9177/svn branch:6.2 --- diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 958b2a781e..969baa160f 100755 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -44,1738 +44,1738 @@ import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent; * Entry point classes define onModuleLoad(). */ public class ApplicationConnection { - private static final String MODIFIED_CLASSNAME = "v-modified"; + private static final String MODIFIED_CLASSNAME = "v-modified"; - private static final String REQUIRED_CLASSNAME_EXT = "-required"; + private static final String REQUIRED_CLASSNAME_EXT = "-required"; - private static final String ERROR_CLASSNAME_EXT = "-error"; + private static final String ERROR_CLASSNAME_EXT = "-error"; - public static final String VAR_RECORD_SEPARATOR = "\u001e"; + public static final String VAR_RECORD_SEPARATOR = "\u001e"; - public static final String VAR_FIELD_SEPARATOR = "\u001f"; + public static final String VAR_FIELD_SEPARATOR = "\u001f"; - public static final String VAR_BURST_SEPARATOR = "\u001d"; + public static final String VAR_BURST_SEPARATOR = "\u001d"; - public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c"; + 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 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 PARAM_UNLOADBURST = "onunloadburst"; - public static final String ATTRIBUTE_DESCRIPTION = "description"; - public static final String ATTRIBUTE_ERROR = "error"; + public static final String ATTRIBUTE_DESCRIPTION = "description"; + public static final String ATTRIBUTE_ERROR = "error"; - private String uidl_security_key = "init"; + private String uidl_security_key = "init"; - private final HashMap resourcesMap = new HashMap(); + private final HashMap resourcesMap = new HashMap(); - private static Console console; + private static Console console; - private final ArrayList pendingVariables = new ArrayList(); + private final ArrayList pendingVariables = new ArrayList(); - private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap - .create(); + private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap + .create(); - private final WidgetSet widgetSet; + private final WidgetSet widgetSet; - private VContextMenu contextMenu = null; + private VContextMenu contextMenu = null; - private Timer loadTimer; - private Timer loadTimer2; - private Timer loadTimer3; - private Element loadElement; + private Timer loadTimer; + private Timer loadTimer2; + private Timer loadTimer3; + private Element loadElement; - private final VView view; + private final VView view; - private boolean applicationRunning = false; + private boolean applicationRunning = false; - private int activeRequests = 0; + private int activeRequests = 0; - /** Parameters for this application connection loaded from the web-page */ - private final ApplicationConfiguration configuration; + /** 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>(); + /** 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; + /** Timer for automatic refirect to SessionExpiredURL */ + private Timer redirectTimer; - /** redirectTimer scheduling interval in seconds */ - private int sessionExpirationInterval; + /** redirectTimer scheduling interval in seconds */ + private int sessionExpirationInterval; - private ArrayList relativeSizeChanges = new ArrayList();; - private ArrayList componentCaptionSizeChanges = new ArrayList();; + private ArrayList relativeSizeChanges = new ArrayList();; + private ArrayList componentCaptionSizeChanges = new ArrayList();; - private Date requestStartTime; + private Date requestStartTime; - private boolean validatingLayouts = false; + private boolean validatingLayouts = false; - private Set zeroWidthComponents = null; + private Set zeroWidthComponents = null; - private Set zeroHeightComponents = 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) {} - } - } - } - }-*/; - - 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); - }-*/; - - public String getAppUri() { - return configuration.getApplicationUri(); - }; - - 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 = 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. - 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 ? "&" : "?") + "windowName=" + windowName; - } - - if (!forceSync) { - boolean success = false; - 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"); - - switch (response.getStatusCode()) { - case 0: - showCommunicationError("Invalid status code 0 (server down?)"); - return; - // TODO could add more cases - 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 - - syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class)) - .createXmlHTTPRequest(), 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 native void syncSendForce(JavaScriptObject xmlHttpRequest, - String uri, String requestData) - /*-{ - try { - xmlHttpRequest.open("POST", uri, false); - xmlHttpRequest.setRequestHeader("Content-Type", "text/plain;charset=utf-8"); - xmlHttpRequest.send(requestData); - } catch (e) { - // No errors are managed as this is synchronous forceful send that can just fail - } - this.@com.vaadin.terminal.gwt.client.ApplicationConnection::endRequest()(); - }-*/; - - 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)) { - // 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"); - } - } - - 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) - /*-{ - if(JSON && JSON.parse) { - return JSON.parse(jsonText); - } else { - return eval('(' + jsonText + ')'); - } - }-*/; - - private void handleReceivedJSONMessage(Response response) { - final Date start = new Date(); - String jsonText = response.getText(); - // for(;;);[realjson] - jsonText = jsonText.substring(9, jsonText.length() - 1); - 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())); - // 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); - } - - 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, 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("0")) { - ClientExceptionHandler - .displayError("Received update for " - + uidl.getTag() - + ", but there is no such paintable (" - + uidl.getId() + ") rendered."); - } else { - view.updateFromUIDL(uidl, this); - } - } - } catch (final Throwable e) { - ClientExceptionHandler.displayError(e); - } - } - - // 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, this, - zeroHeightComponents, zeroWidthComponents); - zeroHeightComponents = null; - zeroWidthComponents = null; - validatingLayouts = false; - - } - } - - 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(); - } - - /** - * 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 id, Paintable paintable) { - ComponentDetail componentDetail = new ComponentDetail(); - componentDetail.setComponent(paintable); - idToPaintableDetail.put(id, componentDetail); - setPid(((Widget) paintable).getElement(), id); - } - - private native void setPid(Element el, String pid) - /*-{ - el.tkPid = pid; - }-*/; - - public String getPid(Paintable paintable) { - return getPid(((Widget) paintable).getElement()); - } - - public native String getPid(Element el) - /*-{ - return el.tkPid; - }-*/; - - public Element getElementByPid(String pid) { - return ((Widget) getPaintable(pid)).getElement(); - } - - 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); - } - } - - 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); - } - - public void updateVariable(String paintableId, String variableName, - Paintable newValue, boolean immediate) { - String pid = (newValue != null) ? getPid(newValue) : null; - addVariableToQueue(paintableId, variableName, pid, immediate, 'p'); - } - - public void updateVariable(String paintableId, String variableName, - String newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate, 's'); - } - - public void updateVariable(String paintableId, String variableName, - int newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'i'); - } - - public void updateVariable(String paintableId, String variableName, - long newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'l'); - } - - public void updateVariable(String paintableId, String variableName, - float newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'f'); - } - - public void updateVariable(String paintableId, String variableName, - double newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, "" + newValue, immediate, - 'd'); - } - - public void updateVariable(String paintableId, String variableName, - boolean newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue ? "true" - : "false", immediate, 'b'); - } - - public void updateVariable(String paintableId, String variableName, - Object[] values, boolean immediate) { - final StringBuffer buf = new StringBuffer(); - for (int i = 0; i < values.length; i++) { - if (i > 0) { - buf.append(VAR_ARRAYITEM_SEPARATOR); - } - buf.append(values[i].toString()); - } - 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; - } - - // 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 (!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); - } - } - - if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) { - DOM.setElementProperty(component.getElement(), "id", uidl.getId() - .substring(5)); - } - - /* - * 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; - - public void runDescendentsLayout(HasWidgets container) { - if (runningLayout) { - // getConsole().log( - // "Already running descendents layout. Not running again for " - // + Util.getSimpleName(container)); - 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()))); - - } - - 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)); - } - - 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; - } - - 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; - } - - public void captionSizeUpdated(Paintable component) { - componentCaptionSizeChanges.add(component); - } - - public void analyzeLayouts() { - makeUidlRequest("", true, false, true); - } - - 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); - } - - public ApplicationConfiguration getConfiguration() { - return configuration; - } + 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) {} + } + } + } + }-*/; + + 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); + }-*/; + + public String getAppUri() { + return configuration.getApplicationUri(); + }; + + 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 = 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. + 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 ? "&" : "?") + "windowName=" + windowName; + } + + if (!forceSync) { + boolean success = false; + 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"); + + switch (response.getStatusCode()) { + case 0: + showCommunicationError("Invalid status code 0 (server down?)"); + return; + // TODO could add more cases + 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 + + syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class)) + .createXmlHTTPRequest(), 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 native void syncSendForce(JavaScriptObject xmlHttpRequest, + String uri, String requestData) + /*-{ + try { + xmlHttpRequest.open("POST", uri, false); + xmlHttpRequest.setRequestHeader("Content-Type", "text/plain;charset=utf-8"); + xmlHttpRequest.send(requestData); + } catch (e) { + // No errors are managed as this is synchronous forceful send that can just fail + } + this.@com.vaadin.terminal.gwt.client.ApplicationConnection::endRequest()(); + }-*/; + + 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)) { + // 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"); + } + } + + 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(); + String jsonText = response.getText(); + // for(;;);[realjson] + jsonText = jsonText.substring(9, jsonText.length() - 1); + 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())); + // 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); + } + + 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, 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("0")) { + ClientExceptionHandler + .displayError("Received update for " + + uidl.getTag() + + ", but there is no such paintable (" + + uidl.getId() + ") rendered."); + } else { + view.updateFromUIDL(uidl, this); + } + } + } catch (final Throwable e) { + ClientExceptionHandler.displayError(e); + } + } + + // 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, this, + zeroHeightComponents, zeroWidthComponents); + zeroHeightComponents = null; + zeroWidthComponents = null; + validatingLayouts = false; + + } + } + + 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(); + } + + /** + * 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 id, Paintable paintable) { + ComponentDetail componentDetail = new ComponentDetail(); + componentDetail.setComponent(paintable); + idToPaintableDetail.put(id, componentDetail); + setPid(((Widget) paintable).getElement(), id); + } + + private native void setPid(Element el, String pid) + /*-{ + el.tkPid = pid; + }-*/; + + public String getPid(Paintable paintable) { + return getPid(((Widget) paintable).getElement()); + } + + public native String getPid(Element el) + /*-{ + return el.tkPid; + }-*/; + + public Element getElementByPid(String pid) { + return ((Widget) getPaintable(pid)).getElement(); + } + + 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); + } + } + + 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); + } + + public void updateVariable(String paintableId, String variableName, + Paintable newValue, boolean immediate) { + String pid = (newValue != null) ? getPid(newValue) : null; + addVariableToQueue(paintableId, variableName, pid, immediate, 'p'); + } + + public void updateVariable(String paintableId, String variableName, + String newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate, 's'); + } + + public void updateVariable(String paintableId, String variableName, + int newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, "" + newValue, immediate, + 'i'); + } + + public void updateVariable(String paintableId, String variableName, + long newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, "" + newValue, immediate, + 'l'); + } + + public void updateVariable(String paintableId, String variableName, + float newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, "" + newValue, immediate, + 'f'); + } + + public void updateVariable(String paintableId, String variableName, + double newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, "" + newValue, immediate, + 'd'); + } + + public void updateVariable(String paintableId, String variableName, + boolean newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue ? "true" + : "false", immediate, 'b'); + } + + public void updateVariable(String paintableId, String variableName, + Object[] values, boolean immediate) { + final StringBuffer buf = new StringBuffer(); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + buf.append(VAR_ARRAYITEM_SEPARATOR); + } + buf.append(values[i].toString()); + } + 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; + } + + // 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 (!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); + } + } + + if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) { + DOM.setElementProperty(component.getElement(), "id", uidl.getId() + .substring(5)); + } + + /* + * 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; + + public void runDescendentsLayout(HasWidgets container) { + if (runningLayout) { + // getConsole().log( + // "Already running descendents layout. Not running again for " + // + Util.getSimpleName(container)); + 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()))); + + } + + 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)); + } + + 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; + } + + 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; + } + + public void captionSizeUpdated(Paintable component) { + componentCaptionSizeChanges.add(component); + } + + public void analyzeLayouts() { + makeUidlRequest("", true, false, true); + } + + 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); + } + + public ApplicationConfiguration getConfiguration() { + return configuration; + } }