diff options
author | John Ahlroos <john@vaadin.com> | 2014-08-07 16:32:23 +0300 |
---|---|---|
committer | John Ahlroos <john@vaadin.com> | 2014-08-07 16:35:06 +0300 |
commit | ecff9648d1cb3d5cc4bd54d2b1e1c6357429792f (patch) | |
tree | b21f4b599183c157900b0f24d41c94e6af3b08a7 /client | |
parent | e5230e6a2433f5c8a74c66b73e96d0454866d316 (diff) | |
parent | ff47bdd97b03a42dfc812b4dc9ad71fa45ce3827 (diff) | |
download | vaadin-framework-ecff9648d1cb3d5cc4bd54d2b1e1c6357429792f.tar.gz vaadin-framework-ecff9648d1cb3d5cc4bd54d2b1e1c6357429792f.zip |
Merge remote-tracking branch 'origin/master' into grid
Conflicts:
WebContent/release-notes.html
Change-Id: Ie05bea7142134a7a9d655fcdf6ca232fd13c742b
Diffstat (limited to 'client')
30 files changed, 1317 insertions, 391 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 6abcdac487..90aa0a14d6 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; @@ -599,14 +598,23 @@ public class ApplicationConnection implements HasHandlers { } } + /** + * Checks if there is some work to be done on the client side + * + * @return true if the client has some work to be done, false otherwise + */ + private boolean isActive() { + return isWorkPending() || hasActiveRequest() + || isExecutingDeferredCommands(); + } + private native void initializeTestbenchHooks( ComponentLocator componentLocator, String TTAppId) /*-{ var ap = this; var client = {}; client.isActive = $entry(function() { - return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()() - || ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()(); + return ap.@com.vaadin.client.ApplicationConnection::isActive()(); }); var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); if (vi) { @@ -810,7 +818,8 @@ public class ApplicationConnection implements HasHandlers { startRequest(); JSONObject payload = new JSONObject(); - if (!getCsrfToken().equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) { + if (!getCsrfToken().equals( + ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) { payload.put(ApplicationConstants.CSRF_TOKEN, new JSONString( getCsrfToken())); } @@ -1320,6 +1329,30 @@ public class ApplicationConnection implements HasHandlers { } /** + * Checks if the client has running or scheduled commands + */ + private boolean isWorkPending() { + ConnectorMap connectorMap = getConnectorMap(); + JsArrayObject<ServerConnector> connectors = connectorMap + .getConnectorsAsJsArray(); + int size = connectors.size(); + for (int i = 0; i < size; i++) { + ServerConnector conn = connectors.get(i); + ComponentConnector compConn = null; + if (conn instanceof ComponentConnector) { + compConn = (ComponentConnector) conn; + Widget wgt = compConn.getWidget(); + if (wgt instanceof DeferredWorker) { + if (((DeferredWorker) wgt).isWorkPending()) { + return true; + } + } + } + } + return false; + } + + /** * Checks if deferred commands are (potentially) still being executed as a * result of an update from the server. Returns true if a deferred command * might still be executing, false otherwise. This will not work correctly @@ -1483,6 +1516,7 @@ public class ApplicationConnection implements HasHandlers { if (json.containsKey("typeMappings")) { configuration.addComponentMappings( json.getValueMap("typeMappings"), widgetSet); + } VConsole.log("Handling resource dependencies"); @@ -1717,6 +1751,7 @@ public class ApplicationConnection implements HasHandlers { for (int i = 0; i < needsUpdateLength; i++) { String childId = dump.get(i); ServerConnector child = connectorMap.getConnector(childId); + if (child instanceof ComponentConnector && ((ComponentConnector) child) .delegateCaptionHandling()) { @@ -3110,7 +3145,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 +3211,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/DeferredWorker.java b/client/src/com/vaadin/client/DeferredWorker.java new file mode 100644 index 0000000000..53f7c79fe6 --- /dev/null +++ b/client/src/com/vaadin/client/DeferredWorker.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Give widgets the possibility to indicate to the framework that there is work + * scheduled to be executed in the near future and that the framework should + * wait for this work to complete before assuming the UI has reached a steady + * state. + */ +public interface DeferredWorker { + /** + * @returns true, if there are operations pending which must be executed + * before reaching a steady state + */ + public boolean isWorkPending(); +} 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/Util.java b/client/src/com/vaadin/client/Util.java index f175bbe714..49c862006b 100644 --- a/client/src/com/vaadin/client/Util.java +++ b/client/src/com/vaadin/client/Util.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 @@ -16,11 +16,14 @@ package com.vaadin.client; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -36,6 +39,8 @@ import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.KeyEvent; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; @@ -56,9 +61,9 @@ public class Util { /** * Helper method for debugging purposes. - * + * * Stops execution on firefox browsers on a breakpoint. - * + * */ public static native void browserDebugger() /*-{ @@ -86,10 +91,10 @@ public class Util { /** * * Returns the topmost element of from given coordinates. - * + * * TODO fix crossplat issues clientX vs pageX. See quircksmode. Not critical * for vaadin as we scroll div istead of page. - * + * * @param x * @param y * @return the element at given coordinates @@ -98,7 +103,7 @@ public class Util { int clientX, int clientY) /*-{ var el = $wnd.document.elementFromPoint(clientX, clientY); - // Call elementFromPoint two times to make sure IE8 also returns something sensible if the application is running in an iframe + // Call elementFromPoint two times to make sure IE8 also returns something sensible if the application is running in an iframe el = $wnd.document.elementFromPoint(clientX, clientY); if(el != null && el.nodeType == 3) { el = el.parentNode; @@ -110,18 +115,18 @@ public class Util { * This helper method can be called if components size have been changed * outside rendering phase. It notifies components parent about the size * change so it can react. - * + * * When using this method, developer should consider if size changes could * be notified lazily. If lazy flag is true, method will save widget and * wait for a moment until it notifies parents in chunks. This may vastly * optimize layout in various situation. Example: if component have a lot of * images their onload events may fire "layout phase" many times in a short * period. - * + * * @param widget * @param lazy * run componentSizeUpdated lazyly - * + * * @deprecated As of 7.0, use * {@link LayoutManager#setNeedsMeasure(ComponentConnector)} * instead @@ -171,7 +176,7 @@ public class Util { /** * Converts html entities to text. - * + * * @param html * @return escaped string presentation of given html */ @@ -189,7 +194,7 @@ public class Util { /** * Escapes the string so it is safe to write inside an HTML attribute. - * + * * @param attribute * The string to escape * @return An escaped version of <literal>attribute</literal>. @@ -208,9 +213,9 @@ public class Util { /** * Clones given element as in JavaScript. - * + * * Deprecate this if there appears similar method into GWT someday. - * + * * @param element * @param deep * clone child tree also @@ -469,10 +474,28 @@ public class Util { } /** - * Run workaround for webkits overflow auto issue. + * Defers the execution of {@link #runWebkitOverflowAutoFix(Element)} * + * @since + * @param elem + * with overflow auto + */ + public static void runWebkitOverflowAutoFixDeferred(final Element elem) { + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + Util.runWebkitOverflowAutoFix(elem); + } + }); + + } + + /** + * Run workaround for webkits overflow auto issue. + * * See: our bug #2138 and https://bugs.webkit.org/show_bug.cgi?id=21462 - * + * * @param elem * with overflow auto */ @@ -543,7 +566,7 @@ public class Util { * dimension is not specified as relative it will return -1. If the shared * state does not contain width or height specifications this will return * null. - * + * * @param state * @return */ @@ -576,7 +599,7 @@ public class Util { * Checks if a and b are equals using {@link #equals(Object)}. Handles null * values as well. Does not ensure that objects are of the same type. * Assumes that the first object's equals method handle equals properly. - * + * * @param a * The first value to compare * @param b @@ -597,7 +620,7 @@ public class Util { /** * Gets the border-box width for the given element, i.e. element width + * border + padding. Always rounds up to nearest integer. - * + * * @param element * The element to check * @return The border-box width for the element @@ -622,7 +645,7 @@ public class Util { /** * Gets the border-box height for the given element, i.e. element height + * border + padding. Always rounds up to nearest integer. - * + * * @param element * The element to check * @return The border-box height for the element @@ -669,7 +692,7 @@ public class Util { var borderBottomPx = cs.borderBottom; var paddingTopPx = cs.paddingTop; var paddingBottomPx = cs.paddingBottom; - + var height = heightPx.substring(0,heightPx.length-2); var border = borderTopPx.substring(0,borderTopPx.length-2)+borderBottomPx.substring(0,borderBottomPx.length-2); var padding = paddingTopPx.substring(0,paddingTopPx.length-2)+paddingBottomPx.substring(0,paddingBottomPx.length-2); @@ -689,7 +712,7 @@ public class Util { var borderRightPx = cs.borderRight; var paddingLeftPx = cs.paddingLeft; var paddingRightPx = cs.paddingRight; - + var width = widthPx.substring(0,widthPx.length-2); var border = borderLeftPx.substring(0,borderLeftPx.length-2)+borderRightPx.substring(0,borderRightPx.length-2); var padding = paddingLeftPx.substring(0,paddingLeftPx.length-2)+paddingRightPx.substring(0,paddingRightPx.length-2); @@ -719,7 +742,7 @@ public class Util { /** * Detects what is currently the overflow style attribute in given element. - * + * * @param pe * the element to detect * @return true if auto or scroll @@ -741,7 +764,7 @@ public class Util { * A simple helper method to detect "computed style" (aka style sheets + * element styles). Values returned differ a lot depending on browsers. * Always be very careful when using this. - * + * * @param el * the element from which the style property is detected * @param p @@ -752,7 +775,7 @@ public class Util { com.google.gwt.dom.client.Element el, String p) /*-{ try { - + if (el.currentStyle) { // IE return el.currentStyle[p]; @@ -776,9 +799,9 @@ public class Util { * also returned if "element" is part of its caption. If * <literal>element</literal> is not part of any child component, null is * returned. - * + * * This method returns the deepest nested VPaintableWidget. - * + * * @param client * A reference to ApplicationConnection * @param parent @@ -836,7 +859,7 @@ public class Util { /** * Will (attempt) to focus the given DOM Element. - * + * * @param el * the element to focus */ @@ -852,7 +875,7 @@ public class Util { /** * Helper method to find the nearest parent paintable instance by traversing * the DOM upwards from given element. - * + * * @param element * the element to start from */ @@ -870,7 +893,7 @@ public class Util { /** * Helper method to find first instance of given Widget type found by * traversing DOM upwards from given element. - * + * * @param element * the element where to start seeking of Widget * @param class1 @@ -907,7 +930,7 @@ public class Util { /** * Force webkit to redraw an element - * + * * @param element * The element that should be redrawn */ @@ -953,9 +976,9 @@ public class Util { /** * Detaches and re-attaches the element from its parent. The element is * reattached at the same position in the DOM as it was before. - * + * * Does nothing if the element is not attached to the DOM. - * + * * @param element * The element to detach and re-attach */ @@ -990,7 +1013,7 @@ public class Util { /** * Returns the index of the childElement within its parent. - * + * * @param subElement * @return */ @@ -1066,7 +1089,7 @@ public class Util { * Temporarily sets the {@code styleProperty} to {@code tempValue} and then * resets it to its current value. Used mainly to work around rendering * issues in IE (and possibly in other browsers) - * + * * @param element * The target element * @param styleProperty @@ -1089,7 +1112,7 @@ public class Util { * A helper method to return the client position from an event. Returns * position from either first changed touch (if touch event) or from the * event itself. - * + * * @param event * @return */ @@ -1105,7 +1128,7 @@ public class Util { * Find the element corresponding to the coordinates in the passed mouse * event. Please note that this is not always the same as the target of the * event e.g. if event capture is used. - * + * * @param event * the mouse event to get coordinates from * @return the element at the coordinates of the event @@ -1122,7 +1145,7 @@ public class Util { * A helper method to return the client position from an event. Returns * position from either first changed touch (if touch event) or from the * event itself. - * + * * @param event * @return */ @@ -1135,7 +1158,7 @@ public class Util { } /** - * + * * @see #getTouchOrMouseClientY(Event) * @param currentGwtEvent * @return @@ -1146,7 +1169,7 @@ public class Util { /** * @see #getTouchOrMouseClientX(Event) - * + * * @param event * @return */ @@ -1215,7 +1238,7 @@ public class Util { /** * Gets the currently focused element. - * + * * @return The active element or null if no active element could be found. */ public native static com.google.gwt.user.client.Element getFocusedElement() @@ -1223,13 +1246,13 @@ public class Util { if ($wnd.document.activeElement) { return $wnd.document.activeElement; } - + return null; }-*/; /** * Gets the currently focused element for Internet Explorer. - * + * * @return The currently focused element * @deprecated Use #getFocusedElement instead */ @@ -1243,7 +1266,7 @@ public class Util { * this method checks that this widget nor any of its parents is hidden. Can * be e.g used to check whether component should react to some events or * not. - * + * * @param widget * @return true if attached and displayed */ @@ -1276,7 +1299,7 @@ public class Util { /** * Scrolls an element into view vertically only. Modified version of * Element.scrollIntoView. - * + * * @param elem * The element to scroll into view */ @@ -1284,11 +1307,11 @@ public class Util { /*-{ var top = elem.offsetTop; var height = elem.offsetHeight; - + if (elem.parentNode != elem.offsetParent) { top -= elem.parentNode.offsetTop; } - + var cur = elem.parentNode; while (cur && (cur.nodeType == 1)) { if (top < cur.scrollTop) { @@ -1297,12 +1320,12 @@ public class Util { if (top + height > cur.scrollTop + cur.clientHeight) { cur.scrollTop = (top + height) - cur.clientHeight; } - + var offsetTop = cur.offsetTop; if (cur.parentNode != cur.offsetParent) { offsetTop -= cur.parentNode.offsetTop; } - + top += offsetTop - cur.scrollTop; cur = cur.parentNode; } @@ -1311,7 +1334,7 @@ public class Util { /** * Checks if the given event is either a touch event or caused by the left * mouse button - * + * * @param event * @return true if the event is a touch event or caused by the left mouse * button, false otherwise @@ -1323,7 +1346,7 @@ public class Util { /** * Performs a shallow comparison of the collections. - * + * * @param collection1 * The first collection * @param collection2 @@ -1369,7 +1392,7 @@ public class Util { /** * Resolve a relative URL to an absolute URL based on the current document's * location. - * + * * @param url * a string with the relative URL to resolve * @return the corresponding absolute URL as a string @@ -1396,4 +1419,161 @@ public class Util { } } + /** + * Wrap a css size value and its unit and translate back and forth to the + * string representation.<br/> + * Eg. 50%, 123px, ... + * + * @since + * @author Vaadin Ltd + */ + @SuppressWarnings("serial") + public static class CssSize implements Serializable { + + /* + * Map the size units with their type. + */ + private static Map<String, Unit> type2Unit = new HashMap<String, Style.Unit>(); + static { + for (Unit unit : Unit.values()) { + type2Unit.put(unit.getType(), unit); + } + } + + /** + * Gets the unit value by its type. + * + * @since + * @param type + * the type of the unit as found in the style. + * @return the unit value. + */ + public static Unit unitByType(String type) { + return type2Unit.get(type); + } + + /* + * Regex to parse the size. + */ + private static final RegExp sizePattern = RegExp + .compile(SharedUtil.SIZE_PATTERN); + + /** + * Parse the size from string format to {@link CssSize}. + * + * @param s + * the size as string. + * @return a {@link CssSize} object. + */ + public static CssSize fromString(String s) { + if (s == null) { + return null; + } + + s = s.trim(); + if ("".equals(s)) { + return null; + } + + float size = 0; + Unit unit = null; + + MatchResult matcher = sizePattern.exec(s); + if (matcher.getGroupCount() > 1) { + + size = Float.parseFloat(matcher.getGroup(1)); + if (size < 0) { + size = -1; + unit = Unit.PX; + + } else { + String symbol = matcher.getGroup(2); + unit = unitByType(symbol); + } + } else { + throw new IllegalArgumentException("Invalid size argument: \"" + + s + "\" (should match " + sizePattern.getSource() + + ")"); + } + return new CssSize(size, unit); + } + + /** + * Creates a {@link CssSize} using a value and its measurement unit. + * + * @since + * @param value + * the value. + * @param unit + * the unit. + * @return the {@link CssSize} object. + */ + public static CssSize fromValueUnit(float value, Unit unit) { + return new CssSize(value, unit); + } + + /* + * The value. + */ + private final float value; + + /* + * The measure unit. + */ + private final Unit unit; + + private CssSize(float value, Unit unit) { + this.value = value; + this.unit = unit; + } + + /** + * Gets the value for this css size. + * + * @return the value. + */ + public float getValue() { + return value; + } + + /** + * Gets the measurement unit for this css size. + * + * @return the unit. + */ + public Unit getUnit() { + return unit; + } + + @Override + public String toString() { + return value + unit.getType(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CssSize) { + CssSize size = (CssSize) obj; + return size.value == value && size.unit == unit; + } + + return false; + } + + /** + * Check whether the two sizes are equals. + * + * @since + * @param cssSize1 + * the first size to compare. + * @param cssSize2 + * the other size to compare with the first one. + * @return true if the two sizes are equals, otherwise false. + */ + public static boolean equals(String cssSize1, String cssSize2) { + return CssSize.fromString(cssSize1).equals(CssSize.fromString(cssSize2)); + } + + } + } diff --git a/client/src/com/vaadin/client/communication/Heartbeat.java b/client/src/com/vaadin/client/communication/Heartbeat.java index 1ff0825f0e..b9493d4520 100644 --- a/client/src/com/vaadin/client/communication/Heartbeat.java +++ b/client/src/com/vaadin/client/communication/Heartbeat.java @@ -124,7 +124,9 @@ public class Heartbeat { @Override public void onError(Request request, Throwable exception) { - getLogger().severe("Exception sending heartbeat: " + exception.getMessage()); + getLogger().severe( + "Exception sending heartbeat: " + + exception.getMessage()); // Notify network observers about response status connection.fireEvent(new ConnectionStatusEvent(0)); // Don't break the loop 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/componentlocator/LocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java index 6eb732bf46..15a156a815 100644 --- a/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java +++ b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java @@ -87,8 +87,7 @@ public interface LocatorStrategy { * @return The DOM element identified by {@code path} or null if the element * could not be located. */ - Element getElementByPathStartingAt(String path, - Element root); + Element getElementByPathStartingAt(String path, Element root); /** * Locates all elements that match a String locator (path) which identifies @@ -119,6 +118,5 @@ public interface LocatorStrategy { * found. */ - List<Element> getElementsByPathStartingAt( - String path, Element root); + List<Element> getElementsByPathStartingAt(String path, Element root); } diff --git a/client/src/com/vaadin/client/debug/internal/HierarchySection.java b/client/src/com/vaadin/client/debug/internal/HierarchySection.java index 1eacf286e8..404ac430df 100644 --- a/client/src/com/vaadin/client/debug/internal/HierarchySection.java +++ b/client/src/com/vaadin/client/debug/internal/HierarchySection.java @@ -108,16 +108,14 @@ public class HierarchySection implements Section { hierarchyPanel.addListener(new SelectConnectorListener() { @Override - public void select(ServerConnector connector, - Element element) { + public void select(ServerConnector connector, Element element) { printState(connector, true); } }); analyzeLayoutsPanel.addListener(new SelectConnectorListener() { @Override - public void select(ServerConnector connector, - Element element) { + public void select(ServerConnector connector, Element element) { printState(connector, true); } }); 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/debug/internal/SelectConnectorListener.java b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java index c3652c78e8..46c8070b30 100644 --- a/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java +++ b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java @@ -33,6 +33,5 @@ public interface SelectConnectorListener { * @param element * selected element of the connector or null if unknown */ - public void select(ServerConnector connector, - Element element); + public void select(ServerConnector connector, Element element); } 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/VCalendarPanel.java b/client/src/com/vaadin/client/ui/VCalendarPanel.java index eaa2292c69..6fc06bb153 100644 --- a/client/src/com/vaadin/client/ui/VCalendarPanel.java +++ b/client/src/com/vaadin/client/ui/VCalendarPanel.java @@ -786,6 +786,21 @@ public class VCalendarPanel extends FocusableFlexTable implements * Updates the calendar and text field with the selected dates. */ public void renderCalendar() { + renderCalendar(true); + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * Updates the calendar and text field with the selected dates. + * + * @param updateDate + * The value false prevents setting the selected date of the + * calendar based on focusedDate. That can be used when only the + * resolution of the calendar is changed and no date has been + * selected. + */ + public void renderCalendar(boolean updateDate) { super.setStylePrimaryName(parent.getStylePrimaryName() + "-calendarpanel"); @@ -798,8 +813,9 @@ public class VCalendarPanel extends FocusableFlexTable implements displayedMonth = new FocusedDate(now.getYear(), now.getMonth(), 1); } - if (getResolution().getCalendarField() <= Resolution.MONTH - .getCalendarField() && focusChangeListener != null) { + if (updateDate + && getResolution().getCalendarField() <= Resolution.MONTH + .getCalendarField() && focusChangeListener != null) { focusChangeListener.focusChanged(new Date(focusedDate.getTime())); } diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index 7f67c39500..6ba0785acc 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -46,6 +46,7 @@ import com.google.gwt.event.dom.client.LoadEvent; import com.google.gwt.event.dom.client.LoadHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.i18n.client.HasDirection.Direction; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; @@ -62,6 +63,7 @@ import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ComputedStyle; import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.UIDL; @@ -252,7 +254,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Shows the popup where the user can see the filtered options - * + * * @param currentSuggestions * The filtered suggestions * @param currentPage @@ -264,10 +266,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, final Collection<FilterSelectSuggestion> currentSuggestions, final int currentPage, final int totalSuggestions) { - if (enableDebug) { - debug("VFS.SP: showSuggestions(" + currentSuggestions + ", " - + currentPage + ", " + totalSuggestions + ")"); - } + debug("VFS.SP: showSuggestions(" + currentSuggestions + ", " + + currentPage + ", " + totalSuggestions + ")"); /* * We need to defer the opening of the popup so that the parent DOM @@ -316,8 +316,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, status.setInnerText(""); } // We don't need to show arrows or statusbar if there is - // only one - // page + // only one page if (totalSuggestions <= pageLength || pageLength == 0) { setPagingEnabled(false); } else { @@ -346,7 +345,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Should the next page button be visible to the user? - * + * * @param active */ private void setNextButtonActive(boolean active) { @@ -366,7 +365,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Should the previous page button be visible to the user - * + * * @param active */ private void setPrevButtonActive(boolean active) { @@ -391,18 +390,13 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, */ public void selectNextItem() { debug("VFS.SP: selectNextItem()"); - final MenuItem cur = menu.getSelectedItem(); - final int index = 1 + menu.getItems().indexOf(cur); + + final int index = menu.getSelectedIndex() + 1; if (menu.getItems().size() > index) { - final MenuItem newSelectedItem = menu.getItems().get(index); - menu.selectItem(newSelectedItem); - tb.setText(newSelectedItem.getText()); - tb.setSelectionRange(lastFilter.length(), newSelectedItem - .getText().length() - lastFilter.length()); - - } else if (hasNextPage()) { - selectPopupItemWhenResponseIsReceived = Select.FIRST; - filterOptions(currentPage + 1, lastFilter); + selectItem(menu.getItems().get(index)); + + } else { + selectNextPage(); } } @@ -411,29 +405,59 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, */ public void selectPrevItem() { debug("VFS.SP: selectPrevItem()"); - final MenuItem cur = menu.getSelectedItem(); - final int index = -1 + menu.getItems().indexOf(cur); + + final int index = menu.getSelectedIndex() - 1; if (index > -1) { - final MenuItem newSelectedItem = menu.getItems().get(index); - menu.selectItem(newSelectedItem); - tb.setText(newSelectedItem.getText()); - tb.setSelectionRange(lastFilter.length(), newSelectedItem - .getText().length() - lastFilter.length()); + selectItem(menu.getItems().get(index)); + } else if (index == -1) { - if (currentPage > 0) { - selectPopupItemWhenResponseIsReceived = Select.LAST; - filterOptions(currentPage - 1, lastFilter); - } + selectPrevPage(); + } else { - final MenuItem newSelectedItem = menu.getItems().get( - menu.getItems().size() - 1); - menu.selectItem(newSelectedItem); - tb.setText(newSelectedItem.getText()); - tb.setSelectionRange(lastFilter.length(), newSelectedItem - .getText().length() - lastFilter.length()); + selectItem(menu.getItems().get(menu.getItems().size() - 1)); } } + /** + * Select the first item of the suggestions list popup. + * + * @since + */ + public void selectFirstItem() { + debug("VFS.SP: selectFirstItem()"); + selectItem(menu.getFirstItem()); + } + + /** + * Select the last item of the suggestions list popup. + * + * @since + */ + public void selectLastItem() { + debug("VFS.SP: selectLastItem()"); + selectItem(menu.getLastItem()); + } + + /* + * Sets the selected item in the popup menu. + */ + private void selectItem(final MenuItem newSelectedItem) { + menu.selectItem(newSelectedItem); + + String text = newSelectedItem != null ? newSelectedItem.getText() + : ""; + + // Set the icon. + FilterSelectSuggestion suggestion = (FilterSelectSuggestion) newSelectedItem + .getCommand(); + setSelectedItemIcon(suggestion.getIconUri()); + + // Set the text. + setText(text); + + menu.updateKeyboardSelectedItem(); + } + /* * Using a timer to scroll up or down the pages so when we receive lots * of consecutive mouse wheel events the pages does not flicker. @@ -486,17 +510,10 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } } - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override public void onBrowserEvent(Event event) { debug("VFS.SP: onBrowserEvent()"); + if (event.getTypeInt() == Event.ONCLICK) { final Element target = DOM.eventGetTarget(event); if (target == up || target == DOM.getChild(up, 0)) { @@ -504,12 +521,24 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } else if (target == down || target == DOM.getChild(down, 0)) { lazyPageScroller.scrollDown(); } + } else if (event.getTypeInt() == Event.ONMOUSEWHEEL) { - int velocity = event.getMouseWheelVelocityY(); - if (velocity > 0) { - lazyPageScroller.scrollDown(); - } else { - lazyPageScroller.scrollUp(); + + boolean scrollNotActive = !menu.isScrollActive(); + + debug("VFS.SP: onBrowserEvent() scrollNotActive: " + + scrollNotActive); + + if (scrollNotActive) { + int velocity = event.getMouseWheelVelocityY(); + + debug("VFS.SP: onBrowserEvent() velocity: " + velocity); + + if (velocity > 0) { + lazyPageScroller.scrollDown(); + } else { + lazyPageScroller.scrollUp(); + } } } @@ -525,7 +554,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * amount of items are visible at a time and a scrollbar or buttons are * visible to change page. If paging is turned of then all options are * rendered into the popup menu. - * + * * @param paging * Should the paging be turned on? */ @@ -546,32 +575,29 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, isPagingEnabled = paging; } - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition - * (int, int) - */ - @Override public void setPosition(int offsetWidth, int offsetHeight) { - debug("VFS.SP: setPosition()"); + debug("VFS.SP: setPosition(" + offsetWidth + ", " + offsetHeight + + ")"); - int top = -1; - int left = -1; + int top = topPosition; + int left = getPopupLeft(); // reset menu size and retrieve its "natural" size menu.setHeight(""); - if (currentPage > 0) { + if (currentPage > 0 && !hasNextPage()) { // fix height to avoid height change when getting to last page menu.fixHeightTo(pageLength); } - offsetHeight = getOffsetHeight(); + final int desiredHeight = offsetHeight = getOffsetHeight(); final int desiredWidth = getMainWidth(); + + debug("VFS.SP: desired[" + desiredWidth + ", " + desiredHeight + + "]"); + Element menuFirstChild = menu.getElement().getFirstChildElement(); - int naturalMenuWidth = menuFirstChild.getOffsetWidth(); + final int naturalMenuWidth = menuFirstChild.getOffsetWidth(); if (popupOuterPadding == -1) { popupOuterPadding = Util.measureHorizontalPaddingAndBorder( @@ -581,7 +607,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (naturalMenuWidth < desiredWidth) { menu.setWidth((desiredWidth - popupOuterPadding) + "px"); menuFirstChild.getStyle().setWidth(100, Unit.PCT); - naturalMenuWidth = desiredWidth; } if (BrowserInfo.get().isIE()) { @@ -589,48 +614,72 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * IE requires us to specify the width for the container * element. Otherwise it will be 100% wide */ - int rootWidth = naturalMenuWidth - popupOuterPadding; + int rootWidth = Math.max(desiredWidth, naturalMenuWidth) + - popupOuterPadding; getContainerElement().getStyle().setWidth(rootWidth, Unit.PX); } - if (offsetHeight + getPopupTop() > Window.getClientHeight() - + Window.getScrollTop()) { + final int vfsHeight = VFilterSelect.this.getOffsetHeight(); + final int spaceAvailableAbove = top - vfsHeight; + final int spaceAvailableBelow = Window.getClientHeight() - top; + if (spaceAvailableBelow < offsetHeight + && spaceAvailableBelow < spaceAvailableAbove) { // popup on top of input instead - top = getPopupTop() - offsetHeight - - VFilterSelect.this.getOffsetHeight(); + top -= offsetHeight + vfsHeight; 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 - * include the margin. - */ - int topMargin = (top - topPosition); - top -= topMargin; + offsetHeight = Math.min(offsetHeight, spaceAvailableBelow); } // fetch real width (mac FF bugs here due GWT popups overflow:auto ) offsetWidth = menuFirstChild.getOffsetWidth(); - if (offsetWidth + getPopupLeft() > Window.getClientWidth() - + Window.getScrollLeft()) { + + if (offsetHeight < desiredHeight) { + int menuHeight = offsetHeight; + if (isPagingEnabled) { + menuHeight -= up.getOffsetHeight() + down.getOffsetHeight() + + status.getOffsetHeight(); + } else { + final ComputedStyle s = new ComputedStyle(menu.getElement()); + menuHeight -= s.getIntProperty("marginBottom") + + s.getIntProperty("marginTop"); + } + + // If the available page height is really tiny then this will be + // negative and an exception will be thrown on setHeight. + int menuElementHeight = menu.getItemOffsetHeight(); + if (menuHeight < menuElementHeight) { + menuHeight = menuElementHeight; + } + + menu.setHeight(menuHeight + "px"); + + final int naturalMenuWidthPlusScrollBar = naturalMenuWidth + + Util.getNativeScrollbarSize(); + if (offsetWidth < naturalMenuWidthPlusScrollBar) { + menu.setWidth(naturalMenuWidthPlusScrollBar + "px"); + } + } + + if (offsetWidth + left > Window.getClientWidth()) { left = VFilterSelect.this.getAbsoluteLeft() - + VFilterSelect.this.getOffsetWidth() - + Window.getScrollLeft() - offsetWidth; + + VFilterSelect.this.getOffsetWidth() - offsetWidth; if (left < 0) { left = 0; + menu.setWidth(Window.getClientWidth() + "px"); } - } else { - left = getPopupLeft(); } + setPopupPosition(left, top); + menu.scrollSelectionIntoView(); } /** * Was the popup just closed? - * + * * @return true if popup was just closed */ public boolean isJustClosed() { @@ -659,7 +708,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Updates style names in suggestion popup to help theme building. - * + * * @param uidl * UIDL for the whole combo box * @param componentState @@ -723,23 +772,34 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, super(true); debug("VFS.SM: constructor()"); addDomHandler(this, LoadEvent.getType()); + + setScrollEnabled(true); } /** * Fixes menus height to use same space as full page would use. Needed - * to avoid height changes when quickly "scrolling" to last page + * to avoid height changes when quickly "scrolling" to last page. */ - public void fixHeightTo(int pagelenth) { + public void fixHeightTo(int pageItemsCount) { + setHeight(getPreferredHeight(pageItemsCount)); + } + + /* + * Gets the preferred height of the menu including pageItemsCount items. + */ + String getPreferredHeight(int pageItemsCount) { if (currentSuggestions.size() > 0) { - final int pixels = pagelenth * (getOffsetHeight() - 2) - / currentSuggestions.size(); - setHeight((pixels + 2) + "px"); + final int pixels = (getPreferredHeight() / currentSuggestions + .size()) * pageItemsCount; + return pixels + "px"; + } else { + return ""; } } /** * Sets the suggestions rendered in the menu - * + * * @param suggestions * The suggestions to be rendered in the menu */ @@ -789,6 +849,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, client.updateVariable(paintableId, "page", 0, false); client.updateVariable(paintableId, "selected", new String[] {}, immediate); + afterUpdateClientVariables(); + suggestionPopup.hide(); return; } @@ -837,6 +899,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, lastNewItemString = enteredItemValue; client.updateVariable(paintableId, "newitem", enteredItemValue, immediate); + afterUpdateClientVariables(); } } else if (item != null && !"".equals(lastFilter) @@ -852,11 +915,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, && !currentSuggestion.key.equals("")) { // An item (not null) selected String text = currentSuggestion.getReplacementString(); - tb.setText(text); + setText(text); selectedOptionKey = currentSuggestion.key; } else { // Null selected - tb.setText(""); + setText(""); selectedOptionKey = null; } } @@ -909,26 +972,75 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } - public void selectFirstItem() { - debug("VFS.SM: selectFirstItem()"); - MenuItem firstItem = getItems().get(0); - selectItem(firstItem); - } - private MenuItem getKeyboardSelectedItem() { return keyboardSelectedItem; } - public void setKeyboardSelectedItem(MenuItem firstItem) { - keyboardSelectedItem = firstItem; + public void setKeyboardSelectedItem(MenuItem menuItem) { + keyboardSelectedItem = menuItem; } + /** + * @deprecated use {@link SuggestionPopup#selectFirstItem()} instead. + */ + @Deprecated + public void selectFirstItem() { + debug("VFS.SM: selectFirstItem()"); + MenuItem firstItem = getItems().get(0); + selectItem(firstItem); + } + + /** + * @deprecated use {@link SuggestionPopup#selectLastItem()} instead. + */ + @Deprecated public void selectLastItem() { debug("VFS.SM: selectLastItem()"); List<MenuItem> items = getItems(); MenuItem lastItem = items.get(items.size() - 1); selectItem(lastItem); } + + /* + * Sets the keyboard item as the current selected one. + */ + void updateKeyboardSelectedItem() { + setKeyboardSelectedItem(getSelectedItem()); + } + + /* + * Gets the height of one menu item. + */ + int getItemOffsetHeight() { + List<MenuItem> items = getItems(); + return items != null && items.size() > 0 ? items.get(0) + .getOffsetHeight() : 0; + } + + /* + * Gets the width of one menu item. + */ + int getItemOffsetWidth() { + List<MenuItem> items = getItems(); + return items != null && items.size() > 0 ? items.get(0) + .getOffsetWidth() : 0; + } + + /** + * Returns true if the scroll is active on the menu element or if the + * menu currently displays the last page with less items then the + * maximum visibility (in which case the scroll is not active, but the + * scroll is active for any other page in general). + */ + @Override + public boolean isScrollActive() { + String height = getElement().getStyle().getHeight(); + String preferredHeight = getPreferredHeight(pageLength); + + return !(height == null || height.length() == 0 || height + .equals(preferredHeight)); + } + } /** @@ -947,7 +1059,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (textInputEnabled) { super.setSelectionRange(pos, length); } else { - super.setSelectionRange(getValue().length(), 0); + super.setSelectionRange(0, getValue().length()); } } } @@ -1273,10 +1385,9 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * Whether to send the options request immediately */ private void filterOptions(int page, String filter, boolean immediate) { - if (enableDebug) { - debug("VFS: filterOptions(" + page + ", " + filter + ", " - + immediate + ")"); - } + debug("VFS: filterOptions(" + page + ", " + filter + ", " + immediate + + ")"); + if (filter.equals(lastFilter) && currentPage == page) { if (!suggestionPopup.isAttached()) { suggestionPopup.showSuggestions(currentSuggestions, @@ -1297,8 +1408,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, waitingForFilteringResponse = true; client.updateVariable(paintableId, "filter", filter, false); client.updateVariable(paintableId, "page", page, immediate); + afterUpdateClientVariables(); + lastFilter = filter; currentPage = page; + } /** For internal use only. May be removed or replaced in the future. */ @@ -1337,7 +1451,19 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (enableDebug) { debug("VFS: setTextboxText(" + text + ")"); } + setText(text); + } + + private void setText(final String text) { + /** + * To leave caret in the beginning of the line. + * SetSelectionRange wouldn't work on IE + * (see #13477) + */ + Direction previousDirection = tb.getDirection(); + tb.setDirection(Direction.RTL); tb.setText(text); + tb.setDirection(previousDirection); } /** @@ -1401,10 +1527,13 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, setPromptingOff(text); } setSelectedItemIcon(suggestion.getIconUri()); + if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) { selectedOptionKey = newKey; client.updateVariable(paintableId, "selected", new String[] { selectedOptionKey }, immediate); + afterUpdateClientVariables(); + // currentPage = -1; // forget the page } suggestionPopup.hide(); @@ -1597,28 +1726,22 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, switch (event.getNativeKeyCode()) { case KeyCodes.KEY_DOWN: suggestionPopup.selectNextItem(); - suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu - .getSelectedItem()); + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); event.stopPropagation(); break; case KeyCodes.KEY_UP: suggestionPopup.selectPrevItem(); - suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu - .getSelectedItem()); + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); event.stopPropagation(); break; case KeyCodes.KEY_PAGEDOWN: - if (hasNextPage()) { - filterOptions(currentPage + 1, lastFilter); - } + selectNextPage(); event.stopPropagation(); break; case KeyCodes.KEY_PAGEUP: - if (currentPage > 0) { - filterOptions(currentPage - 1, lastFilter); - } + selectPrevPage(); event.stopPropagation(); break; case KeyCodes.KEY_TAB: @@ -1664,6 +1787,26 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } + /* + * Show the prev page. + */ + private void selectPrevPage() { + if (currentPage > 0) { + filterOptions(currentPage - 1, lastFilter); + selectPopupItemWhenResponseIsReceived = Select.LAST; + } + } + + /* + * Show the next page. + */ + private void selectNextPage() { + if (hasNextPage()) { + filterOptions(currentPage + 1, lastFilter); + selectPopupItemWhenResponseIsReceived = Select.FIRST; + } + } + /** * Triggered when a key was depressed * @@ -1707,15 +1850,21 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (currentSuggestion != null) { String text = currentSuggestion.getReplacementString(); setPromptingOff(text); + setSelectedItemIcon(currentSuggestion.getIconUri()); + selectedOptionKey = currentSuggestion.key; + } else { if (focused || readonly || !enabled) { setPromptingOff(""); } else { setPromptingOn(); } + setSelectedItemIcon(null); + selectedOptionKey = null; } + lastFilter = ""; suggestionPopup.hide(); } @@ -1837,6 +1986,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (client.hasEventListeners(this, EventId.FOCUS)) { client.updateVariable(paintableId, EventId.FOCUS, "", true); + afterUpdateClientVariables(); } ComponentConnector connector = ConnectorMap.get(client).getConnector( @@ -1913,6 +2063,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (client.hasEventListeners(this, EventId.BLUR)) { client.updateVariable(paintableId, EventId.BLUR, "", true); + afterUpdateClientVariables(); } } @@ -2091,4 +2242,15 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, com.google.gwt.user.client.Element captionElement) { AriaHelper.bindCaption(tb, captionElement); } + + /* + * Anything that should be set after the client updates the server. + */ + private void afterUpdateClientVariables() { + // We need this here to be consistent with the all the calls. + // Then set your specific selection type only after + // client.updateVariable() method call. + selectPopupItemWhenResponseIsReceived = Select.NONE; + } + } diff --git a/client/src/com/vaadin/client/ui/VGridLayout.java b/client/src/com/vaadin/client/ui/VGridLayout.java index 10e5c00a38..17131ce63b 100644 --- a/client/src/com/vaadin/client/ui/VGridLayout.java +++ b/client/src/com/vaadin/client/ui/VGridLayout.java @@ -95,7 +95,7 @@ public class VGridLayout extends ComplexPanel { /** * Returns the column widths measured in pixels - * + * * @return */ protected int[] getColumnWidths() { @@ -104,7 +104,7 @@ public class VGridLayout extends ComplexPanel { /** * Returns the row heights measured in pixels - * + * * @return */ protected int[] getRowHeights() { @@ -113,7 +113,7 @@ public class VGridLayout extends ComplexPanel { /** * Returns the spacing between the cells horizontally in pixels - * + * * @return */ protected int getHorizontalSpacing() { @@ -122,7 +122,7 @@ public class VGridLayout extends ComplexPanel { /** * Returns the spacing between the cells vertically in pixels - * + * * @return */ protected int getVerticalSpacing() { @@ -271,7 +271,7 @@ public class VGridLayout extends ComplexPanel { } for (SpanList l : rowSpans) { for (Cell cell : l.cells) { - if (cell.row >= i && i < cell.row + cell.rowspan) { + if (cell.row <= i && i < cell.row + cell.rowspan) { return true; } } @@ -287,7 +287,7 @@ public class VGridLayout extends ComplexPanel { } for (SpanList l : colSpans) { for (Cell cell : l.cells) { - if (cell.col >= i && i < cell.col + cell.colspan) { + if (cell.col <= i && i < cell.col + cell.colspan) { return true; } } @@ -751,7 +751,7 @@ public class VGridLayout extends ComplexPanel { * Creates a new Cell with the given coordinates. * <p> * For internal use only. May be removed or replaced in the future. - * + * * @param row * @param col * @return @@ -767,7 +767,7 @@ public class VGridLayout extends ComplexPanel { * child component is also returned if "element" is part of its caption. * <p> * For internal use only. May be removed or replaced in the future. - * + * * @param element * An element that is a nested sub element of the root element in * this layout @@ -788,13 +788,13 @@ public class VGridLayout extends ComplexPanel { * child component is also returned if "element" is part of its caption. * <p> * For internal use only. May be removed or replaced in the future. - * + * * @param element * An element that is a nested sub element of the root element in * this layout * @return The Paintable which the element is a part of. Null if the element * belongs to the layout and not to a child. - * + * * @since 7.2 */ public ComponentConnector getComponent(Element element) { diff --git a/client/src/com/vaadin/client/ui/VNativeButton.java b/client/src/com/vaadin/client/ui/VNativeButton.java index 93d8d958d6..8e0dd2bce1 100644 --- a/client/src/com/vaadin/client/ui/VNativeButton.java +++ b/client/src/com/vaadin/client/ui/VNativeButton.java @@ -104,7 +104,9 @@ public class VNativeButton extends Button implements ClickHandler { } clickPending = false; } else if (event.getTypeInt() == Event.ONFOCUS) { - if (BrowserInfo.get().isIE() && clickPending) { + if (BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() < 11 + && clickPending) { /* * The focus event will mess up IE and IE will not trigger the * mouse up event (which in turn triggers the click event) until diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java index d2c3ee2dae..afa13dc337 100644 --- a/client/src/com/vaadin/client/ui/VOverlay.java +++ b/client/src/com/vaadin/client/ui/VOverlay.java @@ -444,7 +444,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * A "thread local" of sorts, set temporarily so that VOverlayImpl knows * which VOverlay is using it, so that it can be attached to the correct * overlay container. - * + * * TODO this is a strange pattern that we should get rid of when possible. */ protected static VOverlay current; @@ -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); } @@ -971,7 +973,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { /* * (non-Javadoc) - * + * * @see com.google.gwt.user.client.ui.PopupPanel#hide() */ @Override @@ -981,7 +983,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { /* * (non-Javadoc) - * + * * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) */ @Override @@ -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/VPopupCalendar.java b/client/src/com/vaadin/client/ui/VPopupCalendar.java index 7dea959bb4..1474ad3b71 100644 --- a/client/src/com/vaadin/client/ui/VPopupCalendar.java +++ b/client/src/com/vaadin/client/ui/VPopupCalendar.java @@ -145,7 +145,7 @@ public class VPopupCalendar extends VTextualDate implements Field, } }); - popup = new VOverlay(true, true, true); + popup = new VOverlay(true, false, true); popup.setOwner(this); FlowPanel wrapper = new FlowPanel(); diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index d88f7426ef..6a1e8664ed 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -80,6 +80,7 @@ import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorMap; +import com.vaadin.client.DeferredWorker; import com.vaadin.client.Focusable; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.StyleConstants; @@ -126,7 +127,7 @@ import com.vaadin.shared.ui.table.TableConstants; */ public class VScrollTable extends FlowPanel implements HasWidgets, ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, - ActionOwner, SubPartAware { + ActionOwner, SubPartAware, DeferredWorker { public static final String STYLENAME = "v-table"; @@ -195,6 +196,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** For internal use only. May be removed or replaced in the future. */ public boolean immediate; + private boolean updatedReqRows = true; + private boolean nullSelectionAllowed = true; private SelectMode selectMode = SelectMode.NONE; @@ -1177,7 +1180,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, @Override public void execute() { - if (firstvisible > 0) { + if (firstvisible >= 0) { firstRowInViewPort = firstvisible; if (firstvisibleOnLastPage > -1) { scrollBodyPanel @@ -2251,13 +2254,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Ensures the column alignments are correct at initial loading. <br/> * (child components widths are correct) */ - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - }); + Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement()); hadScrollBars = willHaveScrollbarz; } @@ -2398,10 +2395,32 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // if client connection is busy, don't bother loading it more VConsole.log("Postponed rowfetch"); schedule(250); + } else if (!updatedReqRows && allRenderedRowsAreNew()) { + + /* + * If all rows are new, there might have been a server-side call + * to Table.setCurrentPageFirstItemIndex(int) In this case, + * scrolling event takes way too late, and all the rows from + * previous viewport to this one were requested. + * + * This should prevent requesting unneeded rows by updating + * reqFirstRow and reqRows before needing them. See (#14135) + */ + + setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); + int last = firstRowInViewPort + (int) (cache_rate * pageLength) + + pageLength - 1; + if (last >= totalRows) { + last = totalRows - 1; + } + setReqRows(last - getReqFirstRow() + 1); + updatedReqRows = true; + schedule(250); } else { int firstRendered = scrollBody.getFirstRendered(); int lastRendered = scrollBody.getLastRendered(); + if (lastRendered > totalRows) { lastRendered = totalRows - 1; } @@ -6272,6 +6291,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public Widget getWidgetForPaintable() { return this; } + } protected class VScrollTableGeneratedRow extends VScrollTableRow { @@ -6720,13 +6740,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets, Util.notifyParentOfSizeChange(VScrollTable.this, rendering); } } - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - }); + Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement()); forceRealignColumnHeaders(); } @@ -6863,13 +6878,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // We must run the fix as a deferred command to prevent it from // overwriting the scroll position with an outdated value, see // #7607. - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - }); + Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement()); } triggerLazyColumnAdjustment(false); @@ -7014,8 +7023,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return; } - if (firstRowInViewPort - pageLength * cache_rate > lastRendered - || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) { + if (allRenderedRowsAreNew()) { // need a totally new set of rows rowRequestHandler .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); @@ -7026,6 +7034,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } rowRequestHandler.setReqRows(last - rowRequestHandler.getReqFirstRow() + 1); + updatedReqRows = false; rowRequestHandler.deferRowFetch(); return; } @@ -7048,6 +7057,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } } + private boolean allRenderedRowsAreNew() { + int firstRowInViewPort = calcFirstRowInViewPort(); + int firstRendered = scrollBody.getFirstRendered(); + int lastRendered = scrollBody.getLastRendered(); + return (firstRowInViewPort - pageLength * cache_rate > lastRendered || firstRowInViewPort + + pageLength + pageLength * cache_rate < firstRendered); + } + protected int calcFirstRowInViewPort() { return (int) Math.ceil(scrollTop / scrollBody.getRowHeight()); } @@ -7941,4 +7958,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, addCloseHandler.removeHandler(); } } + + /* + * Return true if component need to perform some work and false otherwise. + */ + @Override + public boolean isWorkPending() { + return lazyAdjustColumnWidths.isRunning(); + } } 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/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 1cee727bc9..495e230156 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.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 @@ -73,7 +73,7 @@ import com.vaadin.shared.ui.window.WindowRole; /** * "Sub window" component. - * + * * @author Vaadin Ltd */ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, @@ -233,7 +233,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /* * Stores the element that has focus in the application UI when the * window is opened, so it can be restored when the window closes. - * + * * This is currently implemented for the case when one non-modal window * can be open at the same time, and the focus is not changed while the * window is open. @@ -253,7 +253,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /* * Restores the previously stored focused element. - * + * * When the focus was changed outside the window while the window was * open, the originally stored element is restored. */ @@ -295,7 +295,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Returns true if this window is the topmost VWindow - * + * * @return */ private boolean isActive() { @@ -437,7 +437,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * is prevented. * <p> * This message is not visible on the screen. - * + * * @param topMessage * String provided when the user navigates with Shift-Tab keys to * the top of the window @@ -452,7 +452,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * key is prevented. * <p> * This message is not visible on the screen. - * + * * @param bottomMessage * String provided when the user navigates with the Tab key to * the bottom of the window @@ -465,7 +465,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * Gets the message that is provided to users of assistive devices when the * user reaches the top of the window when leaving a window with the tab key * is prevented. - * + * * @return the top message */ public String getTabStopTopAssistiveText() { @@ -476,7 +476,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * Gets the message that is provided to users of assistive devices when the * user reaches the bottom of the window when leaving a window with the tab * key is prevented. - * + * * @return the bottom message */ public String getTabStopBottomAssistiveText() { @@ -554,41 +554,11 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /* * Shake up the DOM a bit to make the window shed unnecessary - * scrollbars and resize correctly afterwards. This resulting code - * took over a week to summon forth, and involved some pretty hairy - * black magic. Don't touch it unless you know what you're doing! - * Fixes ticket #11994 + * scrollbars and resize correctly afterwards. The version fixing + * ticket #11994 which was changing the size to 110% was replaced + * with this due to ticket #12943 */ - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - final com.google.gwt.dom.client.Element scrollable = contents - .getFirstChildElement(); - - // Adjusting the width or height may change the scroll - // position, so store the current position - int horizontalScrollPosition = scrollable.getScrollLeft(); - int verticalScrollPosition = scrollable.getScrollTop(); - - final String oldWidth = scrollable.getStyle().getWidth(); - final String oldHeight = scrollable.getStyle().getHeight(); - - scrollable.getStyle().setWidth(110, Unit.PCT); - scrollable.getOffsetWidth(); - scrollable.getStyle().setProperty("width", oldWidth); - - scrollable.getStyle().setHeight(110, Unit.PCT); - scrollable.getOffsetHeight(); - scrollable.getStyle().setProperty("height", oldHeight); - - // Restore the scroll position - scrollable.setScrollLeft(horizontalScrollPosition); - scrollable.setScrollTop(verticalScrollPosition); - - updateContentsSize(); - positionOrSizeUpdated(); - } - }); + Util.runWebkitOverflowAutoFix(contents.getFirstChildElement()); } } @@ -616,7 +586,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Sets the closable state of the window. Additionally hides/shows the close * button according to the new state. - * + * * @param closable * true if the window can be closed by the user */ @@ -638,7 +608,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * Returns the closable state of the sub window. If the sub window is * closable a decoration (typically an X) is shown to the user. By clicking * on the X the user can close the window. - * + * * @return true if the sub window is closable */ protected boolean isClosable() { @@ -670,7 +640,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * correctly if clicking on the "close" button in the window header but * closing the window from a button for example in the window will fail. * Symptom described in #10776 - * + * * The problematic part is that for the focus to be returned correctly * an input element needs to be focused in the root panel. Focusing some * other element apparently won't work. @@ -902,7 +872,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Setter for the text for assistive devices the window caption is prefixed * with. - * + * * @param assistivePrefix * the assistivePrefix to set */ @@ -913,7 +883,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Getter for the text for assistive devices the window caption is prefixed * with. - * + * * @return the assistivePrefix */ public String getAssistivePrefix() { @@ -923,7 +893,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Setter for the text for assistive devices the window caption is postfixed * with. - * + * * @param assistivePostfix * the assistivePostfix to set */ @@ -934,7 +904,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Getter for the text for assistive devices the window caption is postfixed * with. - * + * * @return the assistivePostfix */ public String getAssistivePostfix() { @@ -1086,14 +1056,14 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * TODO check if we need to support this with touch based devices. - * + * * Checks if the cursor was inside the browser content area when the event * happened. - * + * * @param event * The event to be checked * @return true, if the cursor is inside the browser content area - * + * * false, otherwise */ private boolean cursorInsideBrowserContentArea(Event event) { @@ -1382,7 +1352,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * assistive devices when it is opened. * <p> * When the provided array is empty, an existing description is removed. - * + * * @param connectors * with the connectors of the widgets to use as description */ @@ -1420,7 +1390,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * Gets the connectors that are used as assistive description. Text * contained in these connectors will be read by assistive devices when the * window is opened. - * + * * @return list of previously set connectors */ public List<Connector> getAssistiveDescription() { @@ -1429,19 +1399,19 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Sets the WAI-ARIA role the window. - * + * * This role defines how an assistive device handles a window. Available * roles are alertdialog and dialog (@see <a * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles * Model</a>). - * + * * The default role is dialog. - * + * * @param role * WAI-ARIA role to set for the window */ public void setWaiAriaRole(WindowRole role) { - if ("alertdialog".equals(role)) { + if (role == WindowRole.ALERTDIALOG) { Roles.getAlertdialogRole().set(getElement()); } else { Roles.getDialogRole().set(getElement()); @@ -1455,7 +1425,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * The value of the parameter doTabStop is stored and used for non-modal * windows. For modal windows, the handlers are always registered, while * preserving the stored value. - * + * * @param doTabStop * true to prevent leaving the window, false to allow leaving the * window for non modal windows @@ -1472,9 +1442,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, /** * Adds a Handler for when user moves the window. - * + * * @since 7.1.9 - * + * * @return {@link HandlerRegistration} used to remove the handler */ public HandlerRegistration addMoveHandler(WindowMoveHandler handler) { diff --git a/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java b/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java index 78505d034c..8feb349a9e 100644 --- a/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java +++ b/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java @@ -28,7 +28,6 @@ import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.ui.VFilterSelect; import com.vaadin.client.ui.VFilterSelect.FilterSelectSuggestion; -import com.vaadin.client.ui.menubar.MenuItem; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.combobox.ComboBoxConstants; import com.vaadin.shared.ui.combobox.ComboBoxState; @@ -157,7 +156,15 @@ public class ComboBoxConnector extends AbstractFieldConnector implements } // handle selection (null or a single value) - if (uidl.hasVariable("selected")) { + if (uidl.hasVariable("selected") + + // In case we're switching page no need to update the selection as the + // selection process didn't finish. + // && getWidget().selectPopupItemWhenResponseIsReceived == + // VFilterSelect.Select.NONE + // + ) { + String[] selectedKeys = uidl.getStringArrayVariable("selected"); if (selectedKeys.length > 0) { performSelection(selectedKeys[0]); @@ -169,12 +176,16 @@ public class ComboBoxConnector extends AbstractFieldConnector implements if ((getWidget().waitingForFilteringResponse && getWidget().lastFilter .toLowerCase().equals(uidl.getStringVariable("filter"))) || popupOpenAndCleared) { + getWidget().suggestionPopup.showSuggestions( getWidget().currentSuggestions, getWidget().currentPage, getWidget().totalMatches); + getWidget().waitingForFilteringResponse = false; + if (!getWidget().popupOpenerClicked && getWidget().selectPopupItemWhenResponseIsReceived != VFilterSelect.Select.NONE) { + // we're paging w/ arrows Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override @@ -222,26 +233,16 @@ public class ComboBoxConnector extends AbstractFieldConnector implements */ private void navigateItemAfterPageChange() { if (getWidget().selectPopupItemWhenResponseIsReceived == VFilterSelect.Select.LAST) { - getWidget().suggestionPopup.menu.selectLastItem(); + getWidget().suggestionPopup.selectLastItem(); } else { - getWidget().suggestionPopup.menu.selectFirstItem(); + getWidget().suggestionPopup.selectFirstItem(); } - // This is used for paging so we update the keyboard selection - // variable as well. - MenuItem activeMenuItem = getWidget().suggestionPopup.menu - .getSelectedItem(); - getWidget().suggestionPopup.menu - .setKeyboardSelectedItem(activeMenuItem); - - // Update text field to contain the correct text - getWidget().setTextboxText(activeMenuItem.getText()); - getWidget().tb.setSelectionRange( - getWidget().lastFilter.length(), - activeMenuItem.getText().length() - - getWidget().lastFilter.length()); - - getWidget().selectPopupItemWhenResponseIsReceived = VFilterSelect.Select.NONE; // reset + // If you're in between 2 requests both changing the page back and + // forth, you don't want this here, instead you need it before any + // other request. + // getWidget().selectPopupItemWhenResponseIsReceived = + // VFilterSelect.Select.NONE; // reset } private void performSelection(String selectedKey) { diff --git a/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java b/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java index a349eb2993..6f059a7c16 100644 --- a/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java +++ b/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java @@ -87,15 +87,17 @@ public class PopupDateFieldConnector extends TextualDateConnector { .isShowISOWeekNumbers()); if (getWidget().calendar.getResolution() != getWidget() .getCurrentResolution()) { + boolean hasSelectedDate = false; getWidget().calendar.setResolution(getWidget() .getCurrentResolution()); if (getWidget().calendar.getDate() != null && getWidget().getCurrentDate() != null) { + hasSelectedDate = true; getWidget().calendar.setDate((Date) getWidget() .getCurrentDate().clone()); - // force re-render when changing resolution only - getWidget().calendar.renderCalendar(); } + // force re-render when changing resolution only + getWidget().calendar.renderCalendar(hasSelectedDate); } // Force re-render of calendar if locale has changed (#12153) diff --git a/client/src/com/vaadin/client/ui/dd/DDUtil.java b/client/src/com/vaadin/client/ui/dd/DDUtil.java index dfdafe1352..77de1f9b1a 100644 --- a/client/src/com/vaadin/client/ui/dd/DDUtil.java +++ b/client/src/com/vaadin/client/ui/dd/DDUtil.java @@ -58,8 +58,7 @@ public class DDUtil { } public static HorizontalDropLocation getHorizontalDropLocation( - Element element, NativeEvent event, - double leftRightRatio) { + Element element, NativeEvent event, double leftRightRatio) { int clientX = Util.getTouchOrMouseClientX(event); // Event coordinates are relative to the viewport, element absolute diff --git a/client/src/com/vaadin/client/ui/dd/DragImageModifier.java b/client/src/com/vaadin/client/ui/dd/DragImageModifier.java index f08c082a70..45ab5d9bb3 100644 --- a/client/src/com/vaadin/client/ui/dd/DragImageModifier.java +++ b/client/src/com/vaadin/client/ui/dd/DragImageModifier.java @@ -20,7 +20,8 @@ import com.google.gwt.dom.client.Element; /** * Interface implemented by widgets if the drag image used for drag'n'drop * requires additional initialization/configuration. The method - * {@link #modifyDragImage(Element)} is called for each element in the automatically generated drag image. + * {@link #modifyDragImage(Element)} is called for each element in the + * automatically generated drag image. * * @since 7.2 * @author Vaadin Ltd diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBar.java b/client/src/com/vaadin/client/ui/menubar/MenuBar.java index b00665e766..726defafd5 100644 --- a/client/src/com/vaadin/client/ui/menubar/MenuBar.java +++ b/client/src/com/vaadin/client/ui/menubar/MenuBar.java @@ -38,6 +38,7 @@ import java.util.List; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; @@ -74,6 +75,9 @@ import com.vaadin.client.ui.VOverlay; public class MenuBar extends Widget implements PopupListener { private final Element body; + private final Element table; + private final Element outer; + private final ArrayList<MenuItem> items = new ArrayList<MenuItem>(); private MenuBar parentMenu; private PopupPanel popup; @@ -98,7 +102,7 @@ public class MenuBar extends Widget implements PopupListener { public MenuBar(boolean vertical) { super(); - final Element table = DOM.createTable(); + table = DOM.createTable(); body = DOM.createTBody(); DOM.appendChild(table, body); @@ -109,7 +113,7 @@ public class MenuBar extends Widget implements PopupListener { this.vertical = vertical; - final Element outer = DOM.createDiv(); + outer = DOM.createDiv(); DOM.appendChild(outer, table); setElement(outer); @@ -324,6 +328,37 @@ public class MenuBar extends Widget implements PopupListener { return selectedItem; } + /** + * Gets the first item from the menu or null if no items. + * + * @since + * @return the first item from the menu or null if no items. + */ + public MenuItem getFirstItem() { + return items != null && items.size() > 0 ? items.get(0) : null; + } + + /** + * Gest the last item from the menu or null if no items. + * + * @since + * @return the last item from the menu or null if no items. + */ + public MenuItem getLastItem() { + return items != null && items.size() > 0 ? items.get(items.size() - 1) + : null; + } + + /** + * Gets the index of the selected item. + * + * @since + * @return the index of the selected item. + */ + public int getSelectedIndex() { + return items != null ? items.indexOf(getSelectedItem()) : -1; + } + @Override protected void onDetach() { // When the menu is detached, make sure to close all of its children. @@ -468,6 +503,7 @@ public class MenuBar extends Widget implements PopupListener { public void selectItem(MenuItem item) { if (item == selectedItem) { + scrollItemIntoView(item); return; } @@ -480,6 +516,74 @@ public class MenuBar extends Widget implements PopupListener { } selectedItem = item; + + scrollItemIntoView(item); + } + + /* + * Scroll the specified item into view. + */ + private void scrollItemIntoView(MenuItem item) { + if (item != null) { + item.getElement().scrollIntoView(); + } + } + + /** + * Scroll the selected item into view. + * + * @since + */ + public void scrollSelectionIntoView() { + scrollItemIntoView(selectedItem); + } + + /** + * Sets the menu scroll enabled or disabled. + * + * @since + * @param enabled + * the enabled state of the scroll. + */ + public void setScrollEnabled(boolean enabled) { + if (enabled) { + if (vertical) { + outer.getStyle().setOverflowY(Overflow.AUTO); + } else { + outer.getStyle().setOverflowX(Overflow.AUTO); + } + + } else { + if (vertical) { + outer.getStyle().clearOverflowY(); + } else { + outer.getStyle().clearOverflowX(); + } + } + } + + /** + * Gets whether the scroll is activate for this menu. + * + * @since + * @return true if the scroll is active, otherwise false. + */ + public boolean isScrollActive() { + // Element element = getElement(); + // return element.getOffsetHeight() > DOM.getChild(element, 0) + // .getOffsetHeight(); + int outerHeight = outer.getOffsetHeight(); + int tableHeight = table.getOffsetHeight(); + return outerHeight < tableHeight; + } + + /** + * Gets the preferred height of the menu. + * + * @since + */ + protected int getPreferredHeight() { + return table.getOffsetHeight(); } /** diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java index 017d1d1024..56b35cce56 100644 --- a/client/src/com/vaadin/client/ui/table/TableConnector.java +++ b/client/src/com/vaadin/client/ui/table/TableConnector.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 @@ -21,7 +21,6 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.user.client.Command; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; @@ -65,7 +64,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements /* * (non-Javadoc) - * + * * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL, * com.vaadin.client.ApplicationConnection) */ @@ -122,7 +121,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements int previousTotalRows = getWidget().totalRows; getWidget().updateTotalRows(uidl); - boolean totalRowsChanged = (getWidget().totalRows != previousTotalRows); + boolean totalRowsHaveChanged = (getWidget().totalRows != previousTotalRows); getWidget().updateDragMode(uidl); @@ -200,7 +199,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements if (getWidget().headerChangedDuringUpdate) { getWidget().triggerLazyColumnAdjustment(true); } else if (!getWidget().isScrollPositionVisible() - || totalRowsChanged + || totalRowsHaveChanged || getWidget().lastRenderedHeight != getWidget().scrollBody .getOffsetHeight()) { // webkits may still bug with their disturbing scrollbar @@ -210,13 +209,8 @@ public class TableConnector extends AbstractHasComponentsConnector implements // by changing overflows as the length of the contents // *shouldn't* have changed (unless the number of rows // or the height of the widget has also changed) - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - Util.runWebkitOverflowAutoFix(getWidget().scrollBodyPanel - .getElement()); - } - }); + Util.runWebkitOverflowAutoFixDeferred(getWidget().scrollBodyPanel + .getElement()); } } else { getWidget().initializeRows(uidl, rowData); @@ -390,7 +384,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements /** * Shows a saved row context menu if the row for the context menu is still * visible. Does nothing if a context menu has not been saved. - * + * * @param savedContextMenu */ public void showSavedContextMenu(ContextMenuDetails savedContextMenu) { diff --git a/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java b/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java index e406ff5459..e9dc3e1dd7 100644 --- a/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java +++ b/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.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 @@ -16,6 +16,10 @@ package com.vaadin.client.ui.textarea; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.MouseUpEvent; +import com.google.gwt.event.dom.client.MouseUpHandler; +import com.vaadin.client.Util.CssSize; import com.vaadin.client.ui.VTextArea; import com.vaadin.client.ui.textfield.TextFieldConnector; import com.vaadin.shared.ui.Connect; @@ -34,4 +38,54 @@ public class TextAreaConnector extends TextFieldConnector { public VTextArea getWidget() { return (VTextArea) super.getWidget(); } + + @Override + protected void init() { + super.init(); + + getWidget().addMouseUpHandler(new ResizeMouseUpHandler()); + } + + /* + * Workaround to handle the resize on the mouse up. + */ + private class ResizeMouseUpHandler implements MouseUpHandler { + + @Override + public void onMouseUp(MouseUpEvent event) { + Element element = getWidget().getElement(); + + updateSize(element.getStyle().getHeight(), getState().height, + "height"); + updateSize(element.getStyle().getWidth(), getState().width, "width"); + } + + /* + * Update the specified size on the server. + */ + private void updateSize(String sizeText, String stateSizeText, + String sizeType) { + + CssSize stateSize = CssSize.fromString(stateSizeText); + CssSize newSize = CssSize.fromString(sizeText); + + if (stateSize == null && newSize == null) { + return; + + } else if (newSize == null) { + sizeText = ""; + + // Else, if the current stateSize is null, just go ahead and set + // the newSize, so no check on stateSize is needed. + + } else if (stateSize != null && stateSize.equals(newSize)) { + return; + } + + getConnection().updateVariable(getConnectorId(), sizeType, + sizeText, false); + } + + } + } 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()); + } + } |