Change-Id: I70c153c4109428686002f985bfe1cb7cba2c9b31tags/7.3.0.beta1
@@ -49,6 +49,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; | |||
@@ -82,7 +83,7 @@ public class ApplicationConfiguration implements EntryPoint { | |||
return null; | |||
} else { | |||
return value +""; | |||
} | |||
} | |||
}-*/; | |||
/** | |||
@@ -103,7 +104,7 @@ public class ApplicationConfiguration implements EntryPoint { | |||
} else { | |||
// $entry not needed as function is not exported | |||
return @java.lang.Boolean::valueOf(Z)(value); | |||
} | |||
} | |||
}-*/; | |||
/** | |||
@@ -124,7 +125,7 @@ public class ApplicationConfiguration implements EntryPoint { | |||
} else { | |||
// $entry not needed as function is not exported | |||
return @java.lang.Integer::valueOf(I)(value); | |||
} | |||
} | |||
}-*/; | |||
/** | |||
@@ -284,14 +285,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. | |||
* |
@@ -3118,7 +3118,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 +3184,8 @@ public class ApplicationConnection implements HasHandlers { | |||
* @return URI to the current theme | |||
*/ | |||
public String getThemeUri() { | |||
return configuration.getThemeUri(); | |||
return configuration.getVaadinDirUrl() + "themes/" | |||
+ getUIConnector().getActiveTheme(); | |||
} | |||
/** |
@@ -334,7 +334,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 +362,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 +492,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) { |
@@ -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()); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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(); |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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, | |||
@@ -61,9 +62,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; | |||
@@ -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, | |||
}); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -549,8 +549,6 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
private boolean resizeLazy = false; | |||
private String theme; | |||
private Navigator navigator; | |||
private PushConnection pushConnection = null; | |||
@@ -633,7 +631,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
this.embedId = embedId; | |||
// Actual theme - used for finding CustomLayout templates | |||
theme = request.getParameter("theme"); | |||
getState(false).theme = request.getParameter("theme"); | |||
getPage().init(request); | |||
@@ -1135,12 +1133,31 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
} | |||
/** | |||
* Gets the theme that was used when the UI was initialized. | |||
* Gets the theme currently in use by this UI | |||
* | |||
* @return the theme name | |||
*/ | |||
public String getTheme() { | |||
return theme; | |||
return getState(false).theme; | |||
} | |||
/** | |||
* Sets the theme currently in use by this UI | |||
* <p> | |||
* Calling this method will remove the old theme (CSS file) from the | |||
* application and add the new theme. | |||
* <p> | |||
* Note that this method is NOT SAFE to call in a portal environment or | |||
* other environment where there are multiple UIs on the same page. The old | |||
* CSS file will be removed even if there are other UIs on the page which | |||
* are still using it. | |||
* | |||
* @since | |||
* @param theme | |||
* The new theme name | |||
*/ | |||
public void setTheme(String theme) { | |||
getState().theme = theme; | |||
} | |||
/** |
@@ -62,6 +62,7 @@ public class UIState extends TabIndexState { | |||
* Configuration for the push channel | |||
*/ | |||
public PushConfigurationState pushConfiguration = new PushConfigurationState(); | |||
public String theme; | |||
{ | |||
primaryStyleName = "v-ui"; | |||
// Default is 1 for legacy reasons | |||
@@ -95,7 +96,7 @@ public class UIState extends TabIndexState { | |||
NotificationRole role) { | |||
this.prefix = prefix; | |||
this.postfix = postfix; | |||
this.notificationRole = role; | |||
notificationRole = role; | |||
} | |||
} | |||
@@ -16,6 +16,8 @@ | |||
package com.vaadin.tests.tb3; | |||
import static com.vaadin.tests.tb3.TB3Runner.localWebDriverIsUsed; | |||
import java.lang.annotation.ElementType; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
@@ -24,7 +26,6 @@ import java.net.URL; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.runner.RunWith; | |||
@@ -49,13 +50,12 @@ import com.thoughtworks.selenium.webdriven.WebDriverBackedSelenium; | |||
import com.vaadin.server.LegacyApplication; | |||
import com.vaadin.server.UIProvider; | |||
import com.vaadin.testbench.TestBench; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.TestBenchTestCase; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.tests.tb3.MultiBrowserTest.Browser; | |||
import com.vaadin.ui.UI; | |||
import static com.vaadin.tests.tb3.TB3Runner.localWebDriverIsUsed; | |||
/** | |||
* Base class for TestBench 3+ tests. All TB3+ tests in the project should | |||
* extend this class. | |||
@@ -157,7 +157,8 @@ public abstract class AbstractTB3Test extends TestBenchTestCase { | |||
} | |||
protected WebElement getTooltipElement() { | |||
return getDriver().findElement(com.vaadin.testbench.By.className("v-tooltip-text")); | |||
return getDriver().findElement( | |||
com.vaadin.testbench.By.className("v-tooltip-text")); | |||
} | |||
protected Coordinates getCoordinates(TestBenchElement element) { | |||
@@ -184,7 +185,22 @@ public abstract class AbstractTB3Test extends TestBenchTestCase { | |||
* {@link #isPush()}. | |||
*/ | |||
protected void openTestURL() { | |||
driver.get(getTestUrl()); | |||
openTestURL(""); | |||
} | |||
/** | |||
* Opens the given test (defined by {@link #getTestUrl()}, optionally with | |||
* debug window and/or push (depending on {@link #isDebug()} and | |||
* {@link #isPush()}. | |||
*/ | |||
protected void openTestURL(String extraParameters) { | |||
String url = getTestUrl(); | |||
if (url.contains("?")) { | |||
url = url + "&" + extraParameters; | |||
} else { | |||
url = url + "?" + extraParameters; | |||
} | |||
driver.get(url); | |||
} | |||
/** |
@@ -0,0 +1,103 @@ | |||
/* | |||
* Copyright 2000-2013 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.tests.themes; | |||
import com.vaadin.annotations.Theme; | |||
import com.vaadin.server.ThemeResource; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.tests.util.PersonContainer; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.GridLayout; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Image; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.Table; | |||
@Theme("reindeer") | |||
public class ThemeChangeOnTheFly extends AbstractTestUIWithLog { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
Button inject = new Button("Inject blue background"); | |||
inject.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
getPage().getStyles().add( | |||
".v-app { background: blue !important;}"); | |||
} | |||
}); | |||
addComponent(inject); | |||
GridLayout gl = new GridLayout(2, 4); | |||
gl.setCaption("Change theme by clicking a button"); | |||
for (final String theme : new String[] { "reindeer", "runo", | |||
"chameleon", "base", null }) { | |||
Button b = new Button(theme); | |||
b.setId(theme + ""); | |||
b.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
getUI().setTheme(theme); | |||
} | |||
}); | |||
gl.addComponent(b); | |||
} | |||
Table t = new Table(); | |||
PersonContainer pc = PersonContainer.createWithTestData(); | |||
pc.addNestedContainerBean("address"); | |||
t.setContainerDataSource(pc); | |||
gl.addComponent(t, 0, 3, 1, 3); | |||
gl.setRowExpandRatio(3, 1); | |||
gl.setWidth("500px"); | |||
gl.setHeight("800px"); | |||
HorizontalLayout images = new HorizontalLayout(); | |||
images.setSpacing(true); | |||
Label l = new Label("Chameleon theme image in caption"); | |||
l.setIcon(new ThemeResource("img/magnifier.png")); | |||
images.addComponent(l); | |||
Image image = new Image("Runo theme image", new ThemeResource( | |||
"icons/64/ok.png")); | |||
images.addComponent(image); | |||
image = new Image("Reindeer theme image", new ThemeResource( | |||
"button/img/left-focus.png")); | |||
images.addComponent(image); | |||
addComponent(images); | |||
addComponent(gl); | |||
getLayout().setSpacing(true); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Test that you can change theme on the fly"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 2874; | |||
} | |||
} |
@@ -0,0 +1,110 @@ | |||
/* | |||
* Copyright 2000-2013 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.tests.themes; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class ThemeChangeOnTheFlyTest extends MultiBrowserTest { | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.tests.tb3.MultiBrowserTest#getBrowsersToTest() | |||
*/ | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
// Seems like stylesheet onload is not fired on PhantomJS | |||
List<DesiredCapabilities> l = super.getBrowsersToTest(); | |||
l.remove(Browser.PHANTOMJS.getDesiredCapabilities()); | |||
return l; | |||
} | |||
@Test | |||
public void injectedStyleAndThemeChange() throws IOException { | |||
openTestURL(); | |||
$(ButtonElement.class).caption("Inject blue background").first() | |||
.click(); | |||
changeTheme("runo"); | |||
compareScreen("runo-blue-background"); | |||
} | |||
@Test | |||
public void reindeerToOthers() throws IOException { | |||
openTestURL(); | |||
compareScreen("reindeer"); | |||
changeThemeAndCompare("runo"); | |||
changeThemeAndCompare("chameleon"); | |||
changeThemeAndCompare("base"); | |||
} | |||
@Test | |||
public void runoToReindeer() throws IOException { | |||
openTestURL("theme=runo"); | |||
compareScreen("runo"); | |||
changeThemeAndCompare("reindeer"); | |||
} | |||
@Test | |||
public void reindeerToNullToReindeer() throws IOException { | |||
openTestURL(); | |||
changeThemeAndCompare("null"); | |||
changeThemeAndCompare("reindeer"); | |||
} | |||
private void changeThemeAndCompare(String theme) throws IOException { | |||
changeTheme(theme); | |||
compareScreen(theme); | |||
} | |||
private void changeTheme(String theme) { | |||
$(ButtonElement.class).id(theme).click(); | |||
if (theme.equals("null")) { | |||
waitForThemeToChange(""); | |||
} else { | |||
waitForThemeToChange(theme); | |||
} | |||
} | |||
private void waitForThemeToChange(final String theme) { | |||
final WebElement rootDiv = findElement(By | |||
.xpath("//div[contains(@class,'v-app')]")); | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
String rootClass = rootDiv.getAttribute("class").trim(); | |||
String expected = "v-app " + theme; | |||
expected = expected.trim(); | |||
return rootClass.equals(expected); | |||
} | |||
}, 30); | |||
} | |||
} |