summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2014-07-30 07:53:28 +0000
committerHenri Sara <hesara@vaadin.com>2014-08-04 08:27:00 +0000
commit422c08c6145b8c8eaff4f453b5c0276beef27290 (patch)
tree4b21fae0fc435442c2d052ae76298b0fb33e3b43 /client
parent99118ccf946486e140cd1a85ff75f2a6c09f4266 (diff)
downloadvaadin-framework-422c08c6145b8c8eaff4f453b5c0276beef27290.tar.gz
vaadin-framework-422c08c6145b8c8eaff4f453b5c0276beef27290.zip
Allow changing theme on the fly (#2874, #14139, #14124)
* Updates UI and overlay container class names when the theme changes * Initially verifies that the theme has actually been loaded (for the embed case) and class names have been properly set * Forces a state change to all components to re-translate theme:// URLs * Runs a full layout after the new theme has been loaded and activated Change-Id: I5a7391abe1bb467130bbb4660e4829b43f3e4255
Diffstat (limited to 'client')
-rw-r--r--client/src/com/vaadin/client/ApplicationConfiguration.java17
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java5
-rw-r--r--client/src/com/vaadin/client/ResourceLoader.java23
-rw-r--r--client/src/com/vaadin/client/communication/TranslatedURLReference.java45
-rw-r--r--client/src/com/vaadin/client/communication/URLReference_Serializer.java12
-rw-r--r--client/src/com/vaadin/client/debug/internal/InfoSection.java2
-rw-r--r--client/src/com/vaadin/client/ui/AbstractConnector.java24
-rw-r--r--client/src/com/vaadin/client/ui/VOverlay.java6
-rw-r--r--client/src/com/vaadin/client/ui/VUI.java25
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java307
10 files changed, 410 insertions, 56 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java
index 3ccbeba6f3..87c8ea465f 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);
- }
+ }
}-*/;
/**
@@ -285,14 +286,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 b569c0b17c..6e2c6e757c 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -3110,7 +3110,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 + ")");
@@ -3176,7 +3176,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 68a16e8162..ceede263fc 100644
--- a/client/src/com/vaadin/client/ResourceLoader.java
+++ b/client/src/com/vaadin/client/ResourceLoader.java
@@ -375,7 +375,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 7.3
+ * @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() {
@@ -390,11 +403,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]);
}
- };
+ };
}-*/;
/**
@@ -520,12 +533,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..b99f4c6e32
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/TranslatedURLReference.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * A URLReference implementation which does late URL translation to be able to
+ * re-translate URLs if e.g. the theme changes
+ *
+ * @since 7.3
+ * @author Vaadin Ltd
+ */
+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/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java
index c62e2c9824..afa13dc337 100644
--- a/client/src/com/vaadin/client/ui/VOverlay.java
+++ b/client/src/com/vaadin/client/ui/VOverlay.java
@@ -881,7 +881,9 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> {
container.setId(id);
String styles = ac.getUIConnector().getWidget().getParent()
.getStyleName();
- container.addClassName(styles);
+ if (styles != null && !styles.equals("")) {
+ container.addClassName(styles);
+ }
container.addClassName(CLASSNAME_CONTAINER);
RootPanel.get().getElement().appendChild(container);
}
@@ -1059,4 +1061,4 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> {
}
}
}
-} \ No newline at end of file
+}
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..c88fd23eca 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() + " ";
@@ -405,9 +409,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
*/
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,54 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}
}
+ /**
+ * Internal helper to get the <head> tag of the page
+ *
+ * @since 7.3
+ * @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 7.3
+ * @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).
+ // StyleInjectorImplIE always injects to the last style tag on the page.
+ 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 +494,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());
@@ -760,4 +804,229 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
getRpcProxy(DebugWindowServerRpc.class).showServerDebugInfo(
serverConnector);
}
+
+ @OnStateChange("theme")
+ void onThemeChange() {
+ final String oldTheme = activeTheme;
+ final String newTheme = getState().theme;
+ final String oldThemeUrl = getThemeUrl(oldTheme);
+ final String newThemeUrl = getThemeUrl(newTheme);
+
+ if (SharedUtil.equals(oldTheme, newTheme)) {
+ // This should only happen on the initial load when activeTheme has
+ // been updated in init.
+
+ if (newTheme == null) {
+ return;
+ }
+
+ // For the embedded case we cannot be 100% sure that the theme has
+ // been loaded and that the style names have been set.
+
+ if (findStylesheetTag(oldThemeUrl) == null) {
+ // If there is no style tag, load it the normal way (the class
+ // name will be added when theme has been loaded)
+ replaceTheme(null, newTheme, null, newThemeUrl);
+ } else if (!getWidget().getParent().getElement()
+ .hasClassName(newTheme)) {
+ // If only the class name is missing, add that
+ activateTheme(newTheme);
+ }
+ return;
+ }
+
+ 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) {
+ tagToReplace = findStylesheetTag(oldThemeUrl);
+
+ 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);
+ }
+
+ }
+
+ /**
+ * Finds a link tag for a style sheet with the given URL
+ *
+ * @since 7.3
+ * @param url
+ * the URL of the style sheet
+ * @return the link tag or null if no matching link tag was found
+ */
+ private LinkElement findStylesheetTag(String url) {
+ 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())
+ && url.equals(link.getHref())) {
+ return link;
+ }
+ }
+ return 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);
+ VOverlay.getOverlayContainer(getConnection()).removeClassName(
+ activeTheme);
+ }
+
+ activeTheme = newTheme;
+
+ if (newTheme != null) {
+ getWidget().getParent().addStyleName(newTheme);
+ VOverlay.getOverlayContainer(getConnection()).addClassName(
+ activeTheme);
+ }
+
+ 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 7.3
+ * @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 7.3
+ * @return the theme name used by this UI
+ */
+ public String getActiveTheme() {
+ return activeTheme;
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(UIConnector.class.getName());
+ }
+
}