diff options
author | Jouni Koivuviita <jouni@vaadin.com> | 2014-06-27 10:47:29 +0300 |
---|---|---|
committer | Jouni Koivuviita <jouni@vaadin.com> | 2014-06-27 10:48:01 +0300 |
commit | c3e4b560f226b4c544cdab455ecabecd5c85d02c (patch) | |
tree | 4992698626695929fb158282f6a423f0de584891 /client | |
parent | b20adbbded1f0b04dcbc7074b166339eb884dcc1 (diff) | |
parent | 2f18ad6f186051f9540cf13784c0936683b11bae (diff) | |
download | vaadin-framework-c3e4b560f226b4c544cdab455ecabecd5c85d02c.tar.gz vaadin-framework-c3e4b560f226b4c544cdab455ecabecd5c85d02c.zip |
Merge branch 'master' into valo
Change-Id: Iffe877e047ee29dbd4e5b3a6098f82da3d0a47cc
Diffstat (limited to 'client')
10 files changed, 437 insertions, 97 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index c7ef9586c5..d4c1671c65 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -51,6 +51,7 @@ import com.vaadin.client.metadata.ConnectorBundleLoader; import com.vaadin.client.metadata.NoDataException; import com.vaadin.client.metadata.TypeData; import com.vaadin.client.ui.UnknownComponentConnector; +import com.vaadin.client.ui.ui.UIConnector; import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.ui.ui.UIConstants; @@ -84,7 +85,7 @@ public class ApplicationConfiguration implements EntryPoint { return null; } else { return value +""; - } + } }-*/; /** @@ -105,7 +106,7 @@ public class ApplicationConfiguration implements EntryPoint { } else { // $entry not needed as function is not exported return @java.lang.Boolean::valueOf(Z)(value); - } + } }-*/; /** @@ -126,7 +127,7 @@ public class ApplicationConfiguration implements EntryPoint { } else { // $entry not needed as function is not exported return @java.lang.Integer::valueOf(I)(value); - } + } }-*/; /** @@ -286,14 +287,16 @@ public class ApplicationConfiguration implements EntryPoint { return serviceUrl; } + /** + * @return the theme name used when initializing the application + * @deprecated as of 7.3. Use {@link UIConnector#getActiveTheme()} to get the + * theme currently in use + */ + @Deprecated public String getThemeName() { return getJsoConfiguration(id).getConfigString("theme"); } - public String getThemeUri() { - return getVaadinDirUrl() + "themes/" + getThemeName(); - } - /** * Gets the URL of the VAADIN directory on the server. * diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 8bc43dda9a..c39beffd87 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -66,7 +66,6 @@ import com.google.gwt.user.client.Window.ClosingHandler; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConfiguration.ErrorMessage; -import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.communication.HasJavaScriptConnectorHelper; @@ -2499,10 +2498,17 @@ public class ApplicationConnection implements HasHandlers { ApplicationConfiguration.startDependencyLoading(); loader.loadScript(url, resourceLoadListener); - // Preload all remaining - for (int i = 0; i < dependencies.length(); i++) { - String preloadUrl = translateVaadinUri(dependencies.get(i)); - loader.preloadResource(preloadUrl, null); + if (ResourceLoader.supportsInOrderScriptExecution()) { + for (int i = 0; i < dependencies.length(); i++) { + String preloadUrl = translateVaadinUri(dependencies.get(i)); + loader.loadScript(preloadUrl, null); + } + } else { + // Preload all remaining + for (int i = 0; i < dependencies.length(); i++) { + String preloadUrl = translateVaadinUri(dependencies.get(i)); + loader.preloadResource(preloadUrl, null); + } } } @@ -3118,7 +3124,7 @@ public class ApplicationConnection implements HasHandlers { return null; } if (uidlUri.startsWith("theme://")) { - final String themeUri = configuration.getThemeUri(); + final String themeUri = getThemeUri(); if (themeUri == null) { VConsole.error("Theme not set: ThemeResource will not be found. (" + uidlUri + ")"); @@ -3184,7 +3190,8 @@ public class ApplicationConnection implements HasHandlers { * @return URI to the current theme */ public String getThemeUri() { - return configuration.getThemeUri(); + return configuration.getVaadinDirUrl() + "themes/" + + getUIConnector().getActiveTheme(); } /** diff --git a/client/src/com/vaadin/client/ResourceLoader.java b/client/src/com/vaadin/client/ResourceLoader.java index 67b878001e..e88def1a72 100644 --- a/client/src/com/vaadin/client/ResourceLoader.java +++ b/client/src/com/vaadin/client/ResourceLoader.java @@ -204,6 +204,26 @@ public class ResourceLoader { */ public void loadScript(final String scriptUrl, final ResourceLoadListener resourceLoadListener) { + loadScript(scriptUrl, resourceLoadListener, + !supportsInOrderScriptExecution()); + } + + /** + * Load a script and notify a listener when the script is loaded. Calling + * this method when the script is currently loading or already loaded + * doesn't cause the script to be loaded again, but the listener will still + * be notified when appropriate. + * + * + * @param scriptUrl + * url of script to load + * @param resourceLoadListener + * listener to notify when script is loaded + * @param async + * What mode the script.async attribute should be set to + */ + public void loadScript(final String scriptUrl, + final ResourceLoadListener resourceLoadListener, boolean async) { final String url = Util.getAbsoluteUrl(scriptUrl); ResourceLoadEvent event = new ResourceLoadEvent(this, url, false); if (loadedResources.contains(url)) { @@ -236,6 +256,9 @@ public class ResourceLoader { ScriptElement scriptTag = Document.get().createScriptElement(); scriptTag.setSrc(url); scriptTag.setType("text/javascript"); + + scriptTag.setPropertyBoolean("async", async); + addOnloadHandler(scriptTag, new ResourceLoadListener() { @Override public void onLoad(ResourceLoadEvent event) { @@ -252,6 +275,17 @@ public class ResourceLoader { } /** + * The current browser supports script.async='false' for maintaining + * execution order for dynamically-added scripts. + * + * @return Browser supports script.async='false' + */ + public static boolean supportsInOrderScriptExecution() { + return BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() >= 11; + } + + /** * Download a resource and notify a listener when the resource is loaded * without attempting to interpret the resource. When a resource has been * preloaded, it will be present in the browser's cache (provided the HTTP @@ -316,6 +350,11 @@ public class ResourceLoader { * XHR not tested - should work, probably causes other issues -*/ if (BrowserInfo.get().isIE()) { + // If ie11+ for some reason gets a preload request + if (BrowserInfo.get().getBrowserMajorVersion() >= 11) { + throw new RuntimeException( + "Browser doesn't support preloading with text/cache"); + } ScriptElement element = Document.get().createScriptElement(); element.setSrc(url); element.setType("text/cache"); @@ -334,7 +373,20 @@ public class ResourceLoader { } } - private native void addOnloadHandler(Element element, + /** + * Adds an onload listener to the given element, which should be a link or a + * script tag. The listener is called whenever loading is complete or an + * error occurred. + * + * @since + * @param element + * the element to attach a listener to + * @param listener + * the listener to call + * @param event + * the event passed to the listener + */ + public static native void addOnloadHandler(Element element, ResourceLoadListener listener, ResourceLoadEvent event) /*-{ element.onload = $entry(function() { @@ -349,11 +401,11 @@ public class ResourceLoader { element.onreadystatechange = null; listener.@com.vaadin.client.ResourceLoader.ResourceLoadListener::onError(Lcom/vaadin/client/ResourceLoader$ResourceLoadEvent;)(event); }); - element.onreadystatechange = function() { + element.onreadystatechange = function() { if ("loaded" === element.readyState || "complete" === element.readyState ) { element.onload(arguments[0]); } - }; + }; }-*/; /** @@ -479,12 +531,12 @@ public class ResourceLoader { if (rules === undefined) { rules = sheet.rules; } - + if (rules === null) { // Style sheet loaded, but can't access length because of XSS -> assume there's something there return 1; } - + // Return length so we can distinguish 0 (probably 404 error) from normal case. return rules.length; } catch (err) { diff --git a/client/src/com/vaadin/client/communication/TranslatedURLReference.java b/client/src/com/vaadin/client/communication/TranslatedURLReference.java new file mode 100644 index 0000000000..c863b7b796 --- /dev/null +++ b/client/src/com/vaadin/client/communication/TranslatedURLReference.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.communication; + +import com.vaadin.client.ApplicationConnection; +import com.vaadin.shared.communication.URLReference; + +public class TranslatedURLReference extends URLReference { + + private ApplicationConnection connection; + + /** + * @param connection + * the connection to set + */ + public void setConnection(ApplicationConnection connection) { + this.connection = connection; + } + + @Override + public String getURL() { + return connection.translateVaadinUri(super.getURL()); + } + +} diff --git a/client/src/com/vaadin/client/communication/URLReference_Serializer.java b/client/src/com/vaadin/client/communication/URLReference_Serializer.java index 586dd626f0..4ecdc606d2 100644 --- a/client/src/com/vaadin/client/communication/URLReference_Serializer.java +++ b/client/src/com/vaadin/client/communication/URLReference_Serializer.java @@ -1,12 +1,12 @@ /* * Copyright 2000-2014 Vaadin Ltd. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -30,14 +30,16 @@ public class URLReference_Serializer implements JSONSerializer<URLReference> { @Override public URLReference deserialize(Type type, JSONValue jsonValue, ApplicationConnection connection) { - URLReference reference = GWT.create(URLReference.class); + TranslatedURLReference reference = GWT + .create(TranslatedURLReference.class); + reference.setConnection(connection); JSONObject json = (JSONObject) jsonValue; if (json.containsKey(URL_FIELD)) { JSONValue jsonURL = json.get(URL_FIELD); String URL = (String) JsonDecoder.decodeValue( new Type(String.class.getName(), null), jsonURL, null, connection); - reference.setURL(connection.translateVaadinUri(URL)); + reference.setURL(URL); } return reference; } diff --git a/client/src/com/vaadin/client/debug/internal/InfoSection.java b/client/src/com/vaadin/client/debug/internal/InfoSection.java index 23b77a94db..a7a84f5f8f 100644 --- a/client/src/com/vaadin/client/debug/internal/InfoSection.java +++ b/client/src/com/vaadin/client/debug/internal/InfoSection.java @@ -163,7 +163,7 @@ public class InfoSection implements Section { addVersionInfo(configuration); addRow("Widget set", GWT.getModuleName()); - addRow("Theme", connection.getConfiguration().getThemeName()); + addRow("Theme", connection.getUIConnector().getActiveTheme()); String communicationMethodInfo = connection .getCommunicationMethodName(); diff --git a/client/src/com/vaadin/client/ui/AbstractConnector.java b/client/src/com/vaadin/client/ui/AbstractConnector.java index a2e0d9cd54..e93ea0f507 100644 --- a/client/src/com/vaadin/client/ui/AbstractConnector.java +++ b/client/src/com/vaadin/client/ui/AbstractConnector.java @@ -28,6 +28,7 @@ import com.google.gwt.event.shared.HandlerManager; import com.google.web.bindery.event.shared.HandlerRegistration; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.FastStringMap; +import com.vaadin.client.FastStringSet; import com.vaadin.client.JsArrayObject; import com.vaadin.client.Profiler; import com.vaadin.client.ServerConnector; @@ -479,4 +480,27 @@ public abstract class AbstractConnector implements ServerConnector, Set<String> reg = getState().registeredEventListeners; return (reg != null && reg.contains(eventIdentifier)); } + + /** + * Force the connector to recheck its state variables as the variables or + * their meaning might have changed. + * + * @since 7.3 + */ + public void forceStateChange() { + StateChangeEvent event = new FullStateChangeEvent(this); + fireEvent(event); + } + + private static class FullStateChangeEvent extends StateChangeEvent { + public FullStateChangeEvent(ServerConnector connector) { + super(connector, FastStringSet.create()); + } + + @Override + public boolean hasPropertyChanged(String property) { + return true; + } + + } } diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index 7157ef99b3..004072d04a 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -30,7 +30,6 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; @@ -213,8 +212,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, private final Element down = DOM.createDiv(); private final Element status = DOM.createDiv(); - private int desiredHeight = -1; - private boolean isPagingEnabled = true; private long lastAutoClosed; @@ -231,7 +228,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, debug("VFS.SP: constructor()"); setOwner(VFilterSelect.this); menu = new SuggestionMenu(); - menu.getElement().getStyle().setOverflowY(Overflow.AUTO); setWidget(menu); getElement().getStyle().setZIndex(Z_INDEX); @@ -554,12 +550,16 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, public void setPosition(int offsetWidth, int offsetHeight) { debug("VFS.SP: setPosition()"); - int top = getPopupTop(); - int left = getPopupLeft(); + int top = -1; + int left = -1; - if (desiredHeight < 0) { - desiredHeight = offsetHeight; + // reset menu size and retrieve its "natural" size + menu.setHeight(""); + if (currentPage > 0) { + // fix height to avoid height change when getting to last page + menu.fixHeightTo(pageLength); } + offsetHeight = getOffsetHeight(); final int desiredWidth = getMainWidth(); Element menuFirstChild = menu.getElement().getFirstChildElement(); @@ -585,20 +585,16 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, getContainerElement().getStyle().setWidth(rootWidth, Unit.PX); } - final int spaceAvailableBelow = Window.getClientHeight() - - (top - Window.getScrollTop()); - final int spaceAvailableAbove = top - Window.getScrollTop() - - VFilterSelect.this.getOffsetHeight(); - if (spaceAvailableBelow < desiredHeight - && spaceAvailableBelow < spaceAvailableAbove) { + if (offsetHeight + getPopupTop() > Window.getClientHeight() + + Window.getScrollTop()) { // popup on top of input instead - top -= desiredHeight + VFilterSelect.this.getOffsetHeight(); - offsetHeight = desiredHeight; + top = getPopupTop() - offsetHeight + - VFilterSelect.this.getOffsetHeight(); if (top < 0) { - offsetHeight += top; top = 0; } } else { + top = getPopupTop(); /* * Take popup top margin into account. getPopupTop() returns the * top value including the margin but the value we give must not @@ -606,19 +602,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, */ int topMargin = (top - topPosition); top -= topMargin; - offsetHeight = Math.min(desiredHeight, spaceAvailableBelow); - } - - /* - * Resize popup and menu if calculated height doesn't match the - * actual height - */ - if (getOffsetHeight() != offsetHeight) { - int menuHeight = offsetHeight - up.getOffsetHeight() - - down.getOffsetHeight() - status.getOffsetHeight(); - menu.setHeight(menuHeight + "px"); - getContainerElement().getStyle().setHeight(offsetHeight, - Unit.PX); } // fetch real width (mac FF bugs here due GWT popups overflow:auto ) @@ -631,6 +614,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (left < 0) { left = 0; } + } else { + left = getPopupLeft(); } setPopupPosition(left, top); } diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java index df24c3b1c7..eae4f6319d 100644 --- a/client/src/com/vaadin/client/ui/VUI.java +++ b/client/src/com/vaadin/client/ui/VUI.java @@ -48,11 +48,12 @@ import com.vaadin.client.Util; import com.vaadin.client.VConsole; import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; +import com.vaadin.client.ui.ui.UIConnector; import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.ui.ui.UIConstants; /** - * + * */ public class VUI extends SimplePanel implements ResizeHandler, Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable, @@ -62,9 +63,6 @@ public class VUI extends SimplePanel implements ResizeHandler, private static int MONITOR_PARENT_TIMER_INTERVAL = 1000; /** For internal use only. May be removed or replaced in the future. */ - public String theme; - - /** For internal use only. May be removed or replaced in the future. */ public String id; /** For internal use only. May be removed or replaced in the future. */ @@ -319,19 +317,15 @@ public class VUI extends SimplePanel implements ResizeHandler, } } - public String getTheme() { - return theme; - } - /** - * Used to reload host page on theme changes. - * <p> - * For internal use only. May be removed or replaced in the future. + * @return the name of the theme in use by this UI. + * @deprecated as of 7.3. Use {@link UIConnector#getActiveTheme()} instead. */ - public static native void reloadHostPage() - /*-{ - $wnd.location.reload(); - }-*/; + @Deprecated + public String getTheme() { + return ((UIConnector) ConnectorMap.get(connection).getConnector(this)) + .getActiveTheme(); + } /** * Returns true if the body is NOT generated, i.e if someone else has made @@ -530,4 +524,5 @@ public class VUI extends SimplePanel implements ResizeHandler, }); } } + } diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java index 1d2a49cbd1..8997509da6 100644 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java @@ -1,12 +1,12 @@ /* * Copyright 2000-2014 Vaadin Ltd. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,6 +18,7 @@ package com.vaadin.client.ui.ui; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -26,8 +27,10 @@ import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.HeadElement; import com.google.gwt.dom.client.LinkElement; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.StyleElement; import com.google.gwt.dom.client.StyleInjector; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; @@ -51,12 +54,17 @@ import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.Focusable; import com.vaadin.client.Paintable; +import com.vaadin.client.ResourceLoader; +import com.vaadin.client.ResourceLoader.ResourceLoadEvent; +import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.ServerConnector; import com.vaadin.client.UIDL; import com.vaadin.client.VConsole; import com.vaadin.client.ValueMap; +import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.client.ui.AbstractConnector; import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; import com.vaadin.client.ui.ClickEventHandler; import com.vaadin.client.ui.ShortcutActionHandler; @@ -66,6 +74,7 @@ import com.vaadin.client.ui.VUI; import com.vaadin.client.ui.layout.MayScrollChildren; import com.vaadin.client.ui.window.WindowConnector; import com.vaadin.server.Page.Styles; +import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.communication.MethodInvocation; import com.vaadin.shared.ui.ComponentStateUtil; @@ -80,6 +89,7 @@ import com.vaadin.shared.ui.ui.UIClientRpc; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.shared.ui.ui.UIServerRpc; import com.vaadin.shared.ui.ui.UIState; +import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.UI; @Connect(value = UI.class, loadStyle = LoadStyle.EAGER) @@ -88,6 +98,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector private HandlerRegistration childStateChangeHandlerRegistration; + private String activeTheme = null; + private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() { @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { @@ -197,14 +209,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector getWidget().immediate = getState().immediate; getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY); - String newTheme = uidl.getStringAttribute("theme"); - if (getWidget().theme != null && !newTheme.equals(getWidget().theme)) { - // Complete page refresh is needed due css can affect layout - // calculations etc - getWidget().reloadHostPage(); - } else { - getWidget().theme = newTheme; - } // this also implicitly removes old styles String styles = ""; styles += getWidget().getStylePrimaryName() + " "; @@ -399,15 +403,12 @@ public class UIConnector extends AbstractSingleComponentContainerConnector /** * Reads CSS strings and resources injected by {@link Styles#inject} from * the UIDL stream. - * + * * @param uidl * The uidl which contains "css-resource" and "css-string" tags */ private void injectCSS(UIDL uidl) { - final HeadElement head = HeadElement.as(Document.get() - .getElementsByTagName(HeadElement.TAG).getItem(0)); - /* * Search the UIDL stream for CSS resources and strings to be injected. */ @@ -424,8 +425,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector link.setRel("stylesheet"); link.setHref(url); link.setType("text/css"); - head.appendChild(link); - + getHead().appendChild(link); // Check if we have CSS string to inject } else if (cssInjectionsUidl.getTag().equals("css-string")) { for (Iterator<?> it2 = cssInjectionsUidl.getChildIterator(); it2 @@ -437,8 +437,53 @@ public class UIConnector extends AbstractSingleComponentContainerConnector } } + /** + * Internal helper to get the <head> tag of the page + * + * @since + * @return the head element + */ + private HeadElement getHead() { + return HeadElement.as(Document.get() + .getElementsByTagName(HeadElement.TAG).getItem(0)); + } + + /** + * Internal helper for removing any stylesheet with the given URL + * + * @since + * @param url + * the url to match with existing stylesheets + */ + private void removeStylesheet(String url) { + NodeList<Element> linkTags = getHead().getElementsByTagName( + LinkElement.TAG); + for (int i = 0; i < linkTags.getLength(); i++) { + LinkElement link = LinkElement.as(linkTags.getItem(i)); + if (!"stylesheet".equals(link.getRel())) { + continue; + } + if (!"text/css".equals(link.getType())) { + continue; + } + if (url.equals(link.getHref())) { + getHead().removeChild(link); + } + } + } + public void init(String rootPanelId, ApplicationConnection applicationConnection) { + // Create a style tag for style injections so they don't end up in + // the theme tag in IE8-IE10 (we don't want to wipe them out if we + // change theme) + if (BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() < 11) { + StyleElement style = Document.get().createStyleElement(); + style.setType("text/css"); + getHead().appendChild(style); + } + DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN | Event.ONSCROLL); @@ -448,9 +493,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector // the user root.getElement().setInnerHTML(""); - String themeName = applicationConnection.getConfiguration() - .getThemeName(); - root.addStyleName(themeName); + activeTheme = applicationConnection.getConfiguration().getThemeName(); root.add(getWidget()); @@ -538,7 +581,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector /** * Checks if the given sub window is a child of this UI Connector - * + * * @deprecated Should be replaced by a more generic mechanism for getting * non-ComponentConnector children * @param wc @@ -552,7 +595,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector /** * Return an iterator for current subwindows. This method is meant for * testing purposes only. - * + * * @return */ public List<WindowConnector> getSubWindows() { @@ -579,7 +622,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector * public API instead of their state object directly. The page state might * not be an independent state object but can be embedded in UI state. * </p> - * + * * @since 7.1 * @return state object of the page */ @@ -643,10 +686,10 @@ public class UIConnector extends AbstractSingleComponentContainerConnector /** * Tries to scroll the viewport so that the given connector is in view. - * + * * @param componentConnector * The connector which should be visible - * + * */ public void scrollIntoView(final ComponentConnector componentConnector) { if (componentConnector == null) { @@ -740,7 +783,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector /** * Invokes the layout analyzer on the server - * + * * @since 7.1 */ public void analyzeLayouts() { @@ -751,7 +794,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector * Sends a request to the server to print details to console that will help * the developer to locate the corresponding server-side connector in the * source code. - * + * * @since 7.1 * @param serverConnector * the connector to locate @@ -760,4 +803,195 @@ public class UIConnector extends AbstractSingleComponentContainerConnector getRpcProxy(DebugWindowServerRpc.class).showServerDebugInfo( serverConnector); } + + @OnStateChange("theme") + void onThemeChange() { + final String oldTheme = activeTheme; + final String newTheme = getState().theme; + if (SharedUtil.equals(oldTheme, newTheme)) { + // This should only happen on the initial load when activeTheme has + // been updated in init + return; + } + + final String oldThemeUrl = getThemeUrl(oldTheme); + final String newThemeUrl = getThemeUrl(newTheme); + getLogger().info("Changing theme from " + oldTheme + " to " + newTheme); + replaceTheme(oldTheme, newTheme, oldThemeUrl, newThemeUrl); + } + + /** + * Loads the new theme and removes references to the old theme + * + * @param oldTheme + * The name of the old theme + * @param newTheme + * The name of the new theme + * @param oldThemeUrl + * The url of the old theme + * @param newThemeUrl + * The url of the new theme + */ + private void replaceTheme(final String oldTheme, final String newTheme, + String oldThemeUrl, final String newThemeUrl) { + + LinkElement tagToReplace = null; + + if (oldTheme != null) { + NodeList<Element> linkTags = getHead().getElementsByTagName( + LinkElement.TAG); + for (int i = 0; i < linkTags.getLength(); i++) { + final LinkElement link = LinkElement.as(linkTags.getItem(i)); + if ("stylesheet".equals(link.getRel()) + && "text/css".equals(link.getType()) + && oldThemeUrl.equals(link.getHref())) { + tagToReplace = link; + break; + } + } + + if (tagToReplace == null) { + getLogger() + .warning( + "Did not find the link tag for the old theme (" + + oldThemeUrl + + "), adding a new stylesheet for the new theme (" + + newThemeUrl + ")"); + } + } + + if (newTheme != null) { + loadTheme(newTheme, newThemeUrl, tagToReplace); + } else { + if (tagToReplace != null) { + tagToReplace.getParentElement().removeChild(tagToReplace); + } + + activateTheme(null); + } + + } + + /** + * Loads the given theme and replaces the given link element with the new + * theme link element. + * + * @param newTheme + * The name of the new theme + * @param newThemeUrl + * The url of the new theme + * @param tagToReplace + * The link element to replace. If null, then the new link + * element is added at the end. + */ + private void loadTheme(final String newTheme, final String newThemeUrl, + final LinkElement tagToReplace) { + LinkElement newThemeLinkElement = Document.get().createLinkElement(); + newThemeLinkElement.setRel("stylesheet"); + newThemeLinkElement.setType("text/css"); + newThemeLinkElement.setHref(newThemeUrl); + ResourceLoader.addOnloadHandler(newThemeLinkElement, + new ResourceLoadListener() { + + @Override + public void onLoad(ResourceLoadEvent event) { + getLogger().info( + "Loading of " + newTheme + " from " + + newThemeUrl + " completed"); + + if (tagToReplace != null) { + tagToReplace.getParentElement().removeChild( + tagToReplace); + } + activateTheme(newTheme); + } + + @Override + public void onError(ResourceLoadEvent event) { + getLogger().warning( + "Could not load theme from " + + getThemeUrl(newTheme)); + } + }, null); + + if (tagToReplace != null) { + getHead().insertBefore(newThemeLinkElement, tagToReplace); + } else { + getHead().appendChild(newThemeLinkElement); + } + } + + /** + * Activates the new theme. Assumes the theme has been loaded and taken into + * use in the browser. + * + * @since 7.3 + * @param newTheme + */ + private void activateTheme(String newTheme) { + if (activeTheme != null) { + getWidget().getParent().removeStyleName(activeTheme); + } + + activeTheme = newTheme; + + if (newTheme != null) { + getWidget().getParent().addStyleName(newTheme); + } + + forceStateChangeRecursively(UIConnector.this); + getLayoutManager().forceLayout(); + } + + /** + * Force a full recursive recheck of every connector's state variables. + * + * @see #forceStateChange() + * + * @since 7.3 + */ + protected static void forceStateChangeRecursively( + AbstractConnector connector) { + connector.forceStateChange(); + + for (ServerConnector child : connector.getChildren()) { + if (child instanceof AbstractConnector) { + forceStateChangeRecursively((AbstractConnector) child); + } else { + getLogger().warning( + "Could not force state change for unknown connector type: " + + child.getClass().getName()); + } + } + + } + + /** + * Internal helper to get the theme URL for a given theme + * + * @since + * @param theme + * the name of the theme + * @return The URL the theme can be loaded from + */ + private String getThemeUrl(String theme) { + return getConnection().translateVaadinUri( + ApplicationConstants.VAADIN_PROTOCOL_PREFIX + "themes/" + theme + + "/styles" + ".css"); + } + + /** + * Returns the name of the theme currently in used by the UI + * + * @since + * @return the theme name used by this UI + */ + public String getActiveTheme() { + return activeTheme; + } + + private static Logger getLogger() { + return Logger.getLogger(UIConnector.class.getName()); + } + } |