summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2015-09-04 15:05:27 +0300
committerArtur Signell <artur@vaadin.com>2015-09-04 15:05:27 +0300
commitf46be1b2792755cb7d9b111068dd6cf202398c4a (patch)
tree74f7098faee688cc1f1ca1b6ffe9e912338bbcaf /client
parente603ea3cf91e5dd0893b766f27d400947527fba9 (diff)
parentebceef4d44bcd61605fa92fcf7be8d3678537599 (diff)
downloadvaadin-framework-f46be1b2792755cb7d9b111068dd6cf202398c4a.tar.gz
vaadin-framework-f46be1b2792755cb7d9b111068dd6cf202398c4a.zip
Merge remote-tracking branch 'origin/master' into reconnect-dialog
Change-Id: Ie622160a83116c83b255a26bec297f73f3223ac7
Diffstat (limited to 'client')
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java10
-rw-r--r--client/src/com/vaadin/client/BrowserInfo.java16
-rw-r--r--client/src/com/vaadin/client/ComputedStyle.java84
-rw-r--r--client/src/com/vaadin/client/EventHelper.java17
-rw-r--r--client/src/com/vaadin/client/ResourceLoader.java8
-rw-r--r--client/src/com/vaadin/client/SuperDevMode.java6
-rw-r--r--client/src/com/vaadin/client/VTooltip.java3
-rw-r--r--client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java82
-rw-r--r--client/src/com/vaadin/client/connectors/GridConnector.java780
-rw-r--r--client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java383
-rw-r--r--client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java42
-rw-r--r--client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java58
-rw-r--r--client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java148
-rw-r--r--client/src/com/vaadin/client/data/AbstractRemoteDataSource.java61
-rw-r--r--client/src/com/vaadin/client/debug/internal/VDebugWindow.java38
-rw-r--r--client/src/com/vaadin/client/extensions/ResponsiveConnector.java6
-rw-r--r--client/src/com/vaadin/client/ui/AbstractConnector.java2
-rw-r--r--client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java87
-rw-r--r--client/src/com/vaadin/client/ui/FocusableScrollPanel.java2
-rw-r--r--client/src/com/vaadin/client/ui/VButton.java3
-rw-r--r--client/src/com/vaadin/client/ui/VFilterSelect.java46
-rw-r--r--client/src/com/vaadin/client/ui/VFormLayout.java17
-rw-r--r--client/src/com/vaadin/client/ui/VRichTextArea.java2
-rw-r--r--client/src/com/vaadin/client/ui/VScrollTable.java20
-rw-r--r--client/src/com/vaadin/client/ui/VTabsheet.java10
-rw-r--r--client/src/com/vaadin/client/ui/VTextArea.java8
-rw-r--r--client/src/com/vaadin/client/ui/VTree.java15
-rw-r--r--client/src/com/vaadin/client/ui/VUI.java10
-rw-r--r--client/src/com/vaadin/client/ui/VWindow.java42
-rw-r--r--client/src/com/vaadin/client/ui/button/ButtonConnector.java38
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java9
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java7
-rw-r--r--client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java34
-rw-r--r--client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java35
-rw-r--r--client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java44
-rw-r--r--client/src/com/vaadin/client/ui/tree/TreeConnector.java6
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java88
-rw-r--r--client/src/com/vaadin/client/ui/window/WindowConnector.java14
-rw-r--r--client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java225
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java34
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/EditorEvent.java108
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java49
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java34
-rw-r--r--client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java34
-rw-r--r--client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java4
-rw-r--r--client/src/com/vaadin/client/widgets/Escalator.java643
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java874
-rw-r--r--client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java25
48 files changed, 2859 insertions, 1452 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index 70275218cf..6e20908274 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -62,7 +62,6 @@ import com.vaadin.client.ui.VContextMenu;
import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.ui.UIConnector;
-import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.VaadinUriResolver;
import com.vaadin.shared.Version;
import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
@@ -1318,20 +1317,19 @@ public class ApplicationConnection implements HasHandlers {
* before the component is updated so the value is correct if called from
* updatedFromUIDL.
*
- * @param paintable
+ * @param connector
* The connector to register event listeners for
* @param eventIdentifier
* The identifier for the event
* @return true if at least one listener has been registered on server side
* for the event identified by eventIdentifier.
* @deprecated As of 7.0. Use
- * {@link AbstractComponentState#hasEventListener(String)}
- * instead
+ * {@link AbstractConnector#hasEventListener(String)} instead
*/
@Deprecated
- public boolean hasEventListeners(ComponentConnector paintable,
+ public boolean hasEventListeners(ComponentConnector connector,
String eventIdentifier) {
- return paintable.hasEventListener(eventIdentifier);
+ return connector.hasEventListener(eventIdentifier);
}
/**
diff --git a/client/src/com/vaadin/client/BrowserInfo.java b/client/src/com/vaadin/client/BrowserInfo.java
index 8b274623c1..8dcefddcf5 100644
--- a/client/src/com/vaadin/client/BrowserInfo.java
+++ b/client/src/com/vaadin/client/BrowserInfo.java
@@ -30,6 +30,7 @@ public class BrowserInfo {
private static final String BROWSER_OPERA = "op";
private static final String BROWSER_IE = "ie";
+ private static final String BROWSER_EDGE = "edge";
private static final String BROWSER_FIREFOX = "ff";
private static final String BROWSER_SAFARI = "sa";
@@ -171,6 +172,13 @@ public class BrowserInfo {
minorVersionClass = majorVersionClass
+ browserDetails.getBrowserMinorVersion();
browserEngineClass = ENGINE_TRIDENT;
+ } else if (browserDetails.isEdge()) {
+ browserIdentifier = BROWSER_EDGE;
+ majorVersionClass = browserIdentifier
+ + getBrowserMajorVersion();
+ minorVersionClass = majorVersionClass
+ + browserDetails.getBrowserMinorVersion();
+ browserEngineClass = "";
} else if (browserDetails.isOpera()) {
browserIdentifier = BROWSER_OPERA;
majorVersionClass = browserIdentifier
@@ -225,6 +233,10 @@ public class BrowserInfo {
return browserDetails.isIE();
}
+ public boolean isEdge() {
+ return browserDetails.isEdge();
+ }
+
public boolean isFirefox() {
return browserDetails.isFirefox();
}
@@ -245,6 +257,10 @@ public class BrowserInfo {
return isIE() && getBrowserMajorVersion() == 10;
}
+ public boolean isIE11() {
+ return isIE() && getBrowserMajorVersion() == 11;
+ }
+
public boolean isChrome() {
return browserDetails.isChrome();
}
diff --git a/client/src/com/vaadin/client/ComputedStyle.java b/client/src/com/vaadin/client/ComputedStyle.java
index 61cb3c2eb6..66404eeeed 100644
--- a/client/src/com/vaadin/client/ComputedStyle.java
+++ b/client/src/com/vaadin/client/ComputedStyle.java
@@ -155,10 +155,10 @@ public class ComputedStyle {
* the property to retrieve
* @return the double value of the property
*/
- public final int getDoubleProperty(String name) {
+ public final double getDoubleProperty(String name) {
Profiler.enter("ComputedStyle.getDoubleProperty");
String value = getProperty(name);
- int result = parseDoubleNative(value);
+ double result = parseDoubleNative(value);
Profiler.leave("ComputedStyle.getDoubleProperty");
return result;
}
@@ -275,9 +275,87 @@ public class ComputedStyle {
* @return the value from the string before any non-numeric characters or
* NaN if the value cannot be parsed as a number
*/
- private static native int parseDoubleNative(final String value)
+ private static native double parseDoubleNative(final String value)
/*-{
return parseFloat(value);
}-*/;
+ /**
+ * Returns the sum of the top and bottom border width
+ *
+ * @since 7.5.3
+ * @return the sum of the top and bottom border
+ */
+ public double getBorderHeight() {
+ double borderHeight = getDoubleProperty("borderTopWidth");
+ borderHeight += getDoubleProperty("borderBottomWidth");
+
+ return borderHeight;
+ }
+
+ /**
+ * Returns the sum of the left and right border width
+ *
+ * @since 7.5.3
+ * @return the sum of the left and right border
+ */
+ public double getBorderWidth() {
+ double borderWidth = getDoubleProperty("borderLeftWidth");
+ borderWidth += getDoubleProperty("borderRightWidth");
+
+ return borderWidth;
+ }
+
+ /**
+ * Returns the sum of the top and bottom padding
+ *
+ * @since 7.5.3
+ * @return the sum of the top and bottom padding
+ */
+ public double getPaddingHeight() {
+ double paddingHeight = getDoubleProperty("paddingTop");
+ paddingHeight += getDoubleProperty("paddingBottom");
+
+ return paddingHeight;
+ }
+
+ /**
+ * Returns the sum of the top and bottom padding
+ *
+ * @since 7.5.3
+ * @return the sum of the left and right padding
+ */
+ public double getPaddingWidth() {
+ double paddingWidth = getDoubleProperty("paddingLeft");
+ paddingWidth += getDoubleProperty("paddingRight");
+
+ return paddingWidth;
+ }
+
+ /**
+ * Returns the sum of the top and bottom margin
+ *
+ * @since 7.6
+ * @return the sum of the top and bottom margin
+ */
+ public double getMarginHeight() {
+ double marginHeight = getDoubleProperty("marginTop");
+ marginHeight += getDoubleProperty("marginBottom");
+
+ return marginHeight;
+ }
+
+ /**
+ * Returns the sum of the top and bottom margin
+ *
+ * @since 7.6
+ * @return the sum of the left and right margin
+ */
+ public double getMarginWidth() {
+ double marginWidth = getDoubleProperty("marginLeft");
+ marginWidth += getDoubleProperty("marginRight");
+
+ return marginWidth;
+ }
+
}
diff --git a/client/src/com/vaadin/client/EventHelper.java b/client/src/com/vaadin/client/EventHelper.java
index f251215d41..1ee252af0f 100644
--- a/client/src/com/vaadin/client/EventHelper.java
+++ b/client/src/com/vaadin/client/EventHelper.java
@@ -51,7 +51,6 @@ import com.google.gwt.user.client.ui.Widget;
*
*
* </pre>
- *
*/
public class EventHelper {
@@ -69,7 +68,7 @@ public class EventHelper {
*/
public static <T extends ComponentConnector & FocusHandler> HandlerRegistration updateFocusHandler(
T connector, HandlerRegistration handlerRegistration) {
- return updateHandler(connector, FOCUS, handlerRegistration,
+ return updateHandler(connector, connector, FOCUS, handlerRegistration,
FocusEvent.getType(), connector.getWidget());
}
@@ -89,7 +88,7 @@ public class EventHelper {
*/
public static <T extends ComponentConnector & FocusHandler> HandlerRegistration updateFocusHandler(
T connector, HandlerRegistration handlerRegistration, Widget widget) {
- return updateHandler(connector, FOCUS, handlerRegistration,
+ return updateHandler(connector, connector, FOCUS, handlerRegistration,
FocusEvent.getType(), widget);
}
@@ -107,7 +106,7 @@ public class EventHelper {
*/
public static <T extends ComponentConnector & BlurHandler> HandlerRegistration updateBlurHandler(
T connector, HandlerRegistration handlerRegistration) {
- return updateHandler(connector, BLUR, handlerRegistration,
+ return updateHandler(connector, connector, BLUR, handlerRegistration,
BlurEvent.getType(), connector.getWidget());
}
@@ -128,23 +127,21 @@ public class EventHelper {
*/
public static <T extends ComponentConnector & BlurHandler> HandlerRegistration updateBlurHandler(
T connector, HandlerRegistration handlerRegistration, Widget widget) {
- return updateHandler(connector, BLUR, handlerRegistration,
+ return updateHandler(connector, connector, BLUR, handlerRegistration,
BlurEvent.getType(), widget);
}
- private static <H extends EventHandler> HandlerRegistration updateHandler(
- ComponentConnector connector, String eventIdentifier,
+ public static <H extends EventHandler> HandlerRegistration updateHandler(
+ ComponentConnector connector, H handler, String eventIdentifier,
HandlerRegistration handlerRegistration, Type<H> type, Widget widget) {
if (connector.hasEventListener(eventIdentifier)) {
if (handlerRegistration == null) {
- handlerRegistration = widget.addDomHandler((H) connector, type);
+ handlerRegistration = widget.addDomHandler(handler, type);
}
} else if (handlerRegistration != null) {
handlerRegistration.removeHandler();
handlerRegistration = null;
}
return handlerRegistration;
-
}
-
}
diff --git a/client/src/com/vaadin/client/ResourceLoader.java b/client/src/com/vaadin/client/ResourceLoader.java
index 9e9ce5ac49..559768d09c 100644
--- a/client/src/com/vaadin/client/ResourceLoader.java
+++ b/client/src/com/vaadin/client/ResourceLoader.java
@@ -283,8 +283,7 @@ public class ResourceLoader {
* @since 7.2.4
*/
public static boolean supportsInOrderScriptExecution() {
- return BrowserInfo.get().isIE()
- && BrowserInfo.get().getBrowserMajorVersion() >= 11;
+ return BrowserInfo.get().isIE11() || BrowserInfo.get().isEdge();
}
/**
@@ -486,10 +485,11 @@ public class ResourceLoader {
addOnloadHandler(linkElement, new ResourceLoadListener() {
@Override
public void onLoad(ResourceLoadEvent event) {
- // Chrome && IE fires load for errors, must check
+ // Chrome, IE, Edge all fire load for errors, must check
// stylesheet data
if (BrowserInfo.get().isChrome()
- || BrowserInfo.get().isIE()) {
+ || BrowserInfo.get().isIE()
+ || BrowserInfo.get().isEdge()) {
int styleSheetLength = getStyleSheetLength(url);
// Error if there's an empty stylesheet
if (styleSheetLength == 0) {
diff --git a/client/src/com/vaadin/client/SuperDevMode.java b/client/src/com/vaadin/client/SuperDevMode.java
index c72cd73939..f664244715 100644
--- a/client/src/com/vaadin/client/SuperDevMode.java
+++ b/client/src/com/vaadin/client/SuperDevMode.java
@@ -189,7 +189,11 @@ public class SuperDevMode {
if (serverUrl == null || "".equals(serverUrl)) {
serverUrl = "http://localhost:9876/";
} else {
- serverUrl = "http://" + serverUrl + "/";
+ if (serverUrl.contains(":")) {
+ serverUrl = "http://" + serverUrl + "/";
+ } else {
+ serverUrl = "http://" + serverUrl + ":9876/";
+ }
}
if (hasSession(SKIP_RECOMPILE)) {
diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java
index 4e59040298..f3d65cd20a 100644
--- a/client/src/com/vaadin/client/VTooltip.java
+++ b/client/src/com/vaadin/client/VTooltip.java
@@ -89,6 +89,9 @@ public class VTooltip extends VOverlay {
LiveValue.ASSERTIVE);
Roles.getTooltipRole().setAriaRelevantProperty(getElement(),
RelevantValue.ADDITIONS);
+
+ // Tooltip needs to be on top of other VOverlay elements.
+ setZIndex(VOverlay.Z_INDEX + 1);
}
/**
diff --git a/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
new file mode 100644
index 0000000000..8ca2292bc5
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
@@ -0,0 +1,82 @@
+/*
+ * 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.connectors;
+
+import java.util.Collection;
+
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.shared.ui.grid.GridState;
+
+import elemental.json.JsonObject;
+
+/**
+ * Base class for all selection model connectors.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractSelectionModelConnector<T extends SelectionModel<JsonObject>>
+ extends AbstractExtensionConnector {
+
+ @Override
+ public GridConnector getParent() {
+ return (GridConnector) super.getParent();
+ }
+
+ protected Grid<JsonObject> getGrid() {
+ return getParent().getWidget();
+ }
+
+ protected RowHandle<JsonObject> getRowHandle(JsonObject row) {
+ return getGrid().getDataSource().getHandle(row);
+ }
+
+ protected String getRowKey(JsonObject row) {
+ return row != null ? getParent().getRowKey(row) : null;
+ }
+
+ protected abstract T createSelectionModel();
+
+ public abstract static class AbstractSelectionModel implements
+ SelectionModel<JsonObject> {
+
+ @Override
+ public boolean isSelected(JsonObject row) {
+ return row.hasKey(GridState.JSONKEY_SELECTED);
+ }
+
+ @Override
+ public void setGrid(Grid<JsonObject> grid) {
+ // NO-OP
+ }
+
+ @Override
+ public void reset() {
+ // Should not need any actions.
+ }
+
+ @Override
+ public Collection<JsonObject> getSelectedRows() {
+ throw new UnsupportedOperationException(
+ "This client-side selection model "
+ + getClass().getSimpleName()
+ + " does not know selected rows.");
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java
index ef52a429e7..1070a46287 100644
--- a/client/src/com/vaadin/client/connectors/GridConnector.java
+++ b/client/src/com/vaadin/client/connectors/GridConnector.java
@@ -19,33 +19,36 @@ package com.vaadin.client.connectors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
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.NativeEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.DeferredWorker;
import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.TooltipInfo;
import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener;
import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
-import com.vaadin.client.data.DataSource.RowHandle;
-import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.AbstractHasComponentsConnector;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
@@ -59,17 +62,12 @@ import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
+import com.vaadin.client.widget.grid.events.EditorCloseEvent;
+import com.vaadin.client.widget.grid.events.EditorEventHandler;
+import com.vaadin.client.widget.grid.events.EditorMoveEvent;
+import com.vaadin.client.widget.grid.events.EditorOpenEvent;
import com.vaadin.client.widget.grid.events.GridClickEvent;
import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
-import com.vaadin.client.widget.grid.events.SelectAllEvent;
-import com.vaadin.client.widget.grid.events.SelectAllHandler;
-import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel;
-import com.vaadin.client.widget.grid.selection.SelectionEvent;
-import com.vaadin.client.widget.grid.selection.SelectionHandler;
-import com.vaadin.client.widget.grid.selection.SelectionModel;
-import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
-import com.vaadin.client.widget.grid.selection.SelectionModelNone;
-import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
import com.vaadin.client.widget.grid.sort.SortEvent;
import com.vaadin.client.widget.grid.sort.SortHandler;
import com.vaadin.client.widget.grid.sort.SortOrder;
@@ -79,10 +77,8 @@ import com.vaadin.client.widgets.Grid.FooterCell;
import com.vaadin.client.widgets.Grid.FooterRow;
import com.vaadin.client.widgets.Grid.HeaderCell;
import com.vaadin.client.widgets.Grid.HeaderRow;
-import com.vaadin.shared.Connector;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.Connect;
-import com.vaadin.shared.ui.grid.DetailsConnectorChange;
import com.vaadin.shared.ui.grid.EditorClientRpc;
import com.vaadin.shared.ui.grid.EditorServerRpc;
import com.vaadin.shared.ui.grid.GridClientRpc;
@@ -90,7 +86,6 @@ import com.vaadin.shared.ui.grid.GridColumnState;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
-import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode;
import com.vaadin.shared.ui.grid.GridStaticSectionState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
@@ -114,8 +109,8 @@ import elemental.json.JsonValue;
public class GridConnector extends AbstractHasComponentsConnector implements
SimpleManagedLayout, DeferredWorker {
- private static final class CustomCellStyleGenerator implements
- CellStyleGenerator<JsonObject> {
+ private static final class CustomStyleGenerator implements
+ CellStyleGenerator<JsonObject>, RowStyleGenerator<JsonObject> {
@Override
public String getStyle(CellReference<JsonObject> cellReference) {
JsonObject row = cellReference.getRow();
@@ -141,10 +136,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
}
- }
-
- private static final class CustomRowStyleGenerator implements
- RowStyleGenerator<JsonObject> {
@Override
public String getStyle(RowReference<JsonObject> rowReference) {
JsonObject row = rowReference.getRow();
@@ -154,7 +145,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
return null;
}
}
-
}
/**
@@ -169,6 +159,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements
private AbstractFieldConnector editorConnector;
+ private HandlerRegistration errorStateHandler;
+
public CustomGridColumn(String id,
AbstractRendererConnector<Object> rendererConnector) {
super(rendererConnector.getRenderer());
@@ -205,8 +197,54 @@ public class GridConnector extends AbstractHasComponentsConnector implements
return editorConnector;
}
- private void setEditorConnector(AbstractFieldConnector editorConnector) {
+ private void setEditorConnector(
+ final AbstractFieldConnector editorConnector) {
this.editorConnector = editorConnector;
+
+ if (errorStateHandler != null) {
+ errorStateHandler.removeHandler();
+ errorStateHandler = null;
+ }
+
+ // Avoid nesting too deep
+ if (editorConnector == null) {
+ return;
+ }
+
+ errorStateHandler = editorConnector.addStateChangeHandler(
+ "errorMessage", new StateChangeHandler() {
+
+ @Override
+ public void onStateChanged(
+ StateChangeEvent stateChangeEvent) {
+
+ String error = editorConnector.getState().errorMessage;
+
+ if (error == null) {
+ columnToErrorMessage
+ .remove(CustomGridColumn.this);
+ } else {
+ // The error message is formatted as HTML;
+ // therefore, we use this hack to make the
+ // string human-readable.
+ Element e = DOM.createElement("div");
+ e.setInnerHTML(editorConnector.getState().errorMessage);
+ error = getHeaderCaption() + ": "
+ + e.getInnerText();
+
+ columnToErrorMessage.put(CustomGridColumn.this,
+ error);
+ }
+
+ // Editor should not be touched while there's a
+ // request pending.
+ if (editorHandler.currentRequest == null) {
+ getWidget().getEditor().setEditorError(
+ getColumnErrors(),
+ columnToErrorMessage.keySet());
+ }
+ }
+ });
}
}
@@ -281,7 +319,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements
if (column instanceof CustomGridColumn) {
AbstractFieldConnector c = ((CustomGridColumn) column)
.getEditorConnector();
- return c != null ? c.getWidget() : null;
+
+ if (c == null) {
+ return null;
+ }
+
+ return c.getWidget();
} else {
throw new IllegalStateException("Unexpected column type: "
+ column.getClass().getName());
@@ -420,255 +463,105 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
};
- private static class CustomDetailsGenerator implements DetailsGenerator {
+ private class CustomDetailsGenerator implements DetailsGenerator {
- private final Map<Integer, ComponentConnector> indexToDetailsMap = new HashMap<Integer, ComponentConnector>();
+ private final Map<String, ComponentConnector> idToDetailsMap = new HashMap<String, ComponentConnector>();
+ private final Map<String, Integer> idToRowIndex = new HashMap<String, Integer>();
@Override
- @SuppressWarnings("boxing")
public Widget getDetails(int rowIndex) {
- ComponentConnector componentConnector = indexToDetailsMap
- .get(rowIndex);
- if (componentConnector != null) {
- return componentConnector.getWidget();
- } else {
- return null;
- }
- }
-
- public void setDetailsConnectorChanges(
- Set<DetailsConnectorChange> changes) {
- /*
- * To avoid overwriting connectors while moving them about, we'll
- * take all the affected connectors, first all remove those that are
- * removed or moved, then we add back those that are moved or added.
- */
+ JsonObject row = getWidget().getDataSource().getRow(rowIndex);
- /* Remove moved/removed connectors from bookkeeping */
- for (DetailsConnectorChange change : changes) {
- Integer oldIndex = change.getOldIndex();
- Connector removedConnector = indexToDetailsMap.remove(oldIndex);
-
- Connector connector = change.getConnector();
- assert removedConnector == null || connector == null
- || removedConnector.equals(connector) : "Index "
- + oldIndex + " points to " + removedConnector
- + " while " + connector + " was expected";
+ if (!row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
+ || row.getString(GridState.JSONKEY_DETAILS_VISIBLE)
+ .isEmpty()) {
+ return null;
}
- /* Add moved/added connectors to bookkeeping */
- for (DetailsConnectorChange change : changes) {
- Integer newIndex = change.getNewIndex();
- ComponentConnector connector = (ComponentConnector) change
- .getConnector();
-
- if (connector != null) {
- assert newIndex != null : "An existing connector has a missing new index.";
-
- ComponentConnector prevConnector = indexToDetailsMap.put(
- newIndex, connector);
+ String id = row.getString(GridState.JSONKEY_DETAILS_VISIBLE);
+ ComponentConnector componentConnector = idToDetailsMap.get(id);
+ idToRowIndex.put(id, rowIndex);
- assert prevConnector == null : "Connector collision at index "
- + newIndex
- + " between old "
- + prevConnector
- + " and new " + connector;
- }
- }
+ return componentConnector.getWidget();
}
- }
-
- @SuppressWarnings("boxing")
- private static class DetailsConnectorFetcher implements DeferredWorker {
- private static final int FETCH_TIMEOUT_MS = 5000;
-
- public interface Listener {
- void fetchHasBeenScheduled(int id);
-
- void fetchHasReturned(int id);
- }
-
- /** A flag making sure that we don't call scheduleFinally many times. */
- private boolean fetcherHasBeenCalled = false;
-
- /** A rolling counter for unique values. */
- private int detailsFetchCounter = 0;
-
- /** A collection that tracks the amount of requests currently underway. */
- private Set<Integer> pendingFetches = new HashSet<Integer>(5);
-
- private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() {
- @Override
- public void execute() {
- int currentFetchId = detailsFetchCounter++;
- pendingFetches.add(currentFetchId);
- rpc.sendDetailsComponents(currentFetchId);
- fetcherHasBeenCalled = false;
-
- if (listener != null) {
- listener.fetchHasBeenScheduled(currentFetchId);
+ public void updateConnectorHierarchy(List<ServerConnector> children) {
+ Set<String> connectorIds = new HashSet<String>();
+ for (ServerConnector child : children) {
+ if (child instanceof ComponentConnector) {
+ connectorIds.add(child.getConnectorId());
+ idToDetailsMap.put(child.getConnectorId(),
+ (ComponentConnector) child);
}
-
- assert assertRequestDoesNotTimeout(currentFetchId);
}
- };
-
- private DetailsConnectorFetcher.Listener listener = null;
-
- private final GridServerRpc rpc;
- public DetailsConnectorFetcher(GridServerRpc rpc) {
- assert rpc != null : "RPC was null";
- this.rpc = rpc;
- }
-
- public void schedule() {
- if (!fetcherHasBeenCalled) {
- Scheduler.get().scheduleFinally(lazyDetailsFetcher);
- fetcherHasBeenCalled = true;
- }
- }
-
- public void responseReceived(int fetchId) {
-
- if (fetchId < 0) {
- /* Ignore negative fetchIds (they're pushed, not fetched) */
- return;
+ Set<String> removedDetails = new HashSet<String>();
+ for (Entry<String, ComponentConnector> entry : idToDetailsMap
+ .entrySet()) {
+ ComponentConnector connector = entry.getValue();
+ String id = connector.getConnectorId();
+ if (!connectorIds.contains(id)) {
+ removedDetails.add(entry.getKey());
+ if (idToRowIndex.containsKey(id)) {
+ getWidget().setDetailsVisible(idToRowIndex.get(id),
+ false);
+ }
+ }
}
- boolean success = pendingFetches.remove(fetchId);
- assert success : "Received a response with an unidentified fetch id";
-
- if (listener != null) {
- listener.fetchHasReturned(fetchId);
+ for (String id : removedDetails) {
+ idToDetailsMap.remove(id);
+ idToRowIndex.remove(id);
}
}
-
- @Override
- public boolean isWorkPending() {
- return fetcherHasBeenCalled || !pendingFetches.isEmpty();
- }
-
- private boolean assertRequestDoesNotTimeout(final int fetchId) {
- /*
- * This method will not be compiled without asserts enabled. This
- * only makes sure that any request does not time out.
- *
- * TODO Should this be an explicit check? Is it worth the overhead?
- */
- new Timer() {
- @Override
- public void run() {
- assert !pendingFetches.contains(fetchId) : "Fetch id "
- + fetchId + " timed out.";
- }
- }.schedule(FETCH_TIMEOUT_MS);
- return true;
- }
-
- public void setListener(DetailsConnectorFetcher.Listener listener) {
- // if more are needed, feel free to convert this into a collection.
- this.listener = listener;
- }
}
/**
- * The functionality that makes sure that the scroll position is still kept
- * up-to-date even if more details are being fetched lazily.
+ * Class for handling scrolling issues with open details.
+ *
+ * @since 7.5.2
*/
- private class LazyDetailsScrollAdjuster implements DeferredWorker {
-
- private static final int SCROLL_TO_END_ID = -2;
- private static final int NO_SCROLL_SCHEDULED = -1;
-
- private class ScrollStopChecker implements DeferredWorker {
- private final ScheduledCommand checkCommand = new ScheduledCommand() {
- @Override
- public void execute() {
- isScheduled = false;
- if (queuedFetches.isEmpty()) {
- currentRow = NO_SCROLL_SCHEDULED;
- destination = null;
- }
- }
- };
-
- private boolean isScheduled = false;
-
- public void schedule() {
- if (isScheduled) {
- return;
- }
- Scheduler.get().scheduleDeferred(checkCommand);
- isScheduled = true;
- }
+ private class LazyDetailsScroller implements DeferredWorker {
- @Override
- public boolean isWorkPending() {
- return isScheduled;
- }
- }
+ /* Timer value tested to work in our test cluster with slow IE8s. */
+ private static final int DISABLE_LAZY_SCROLL_TIMEOUT = 1500;
- private DetailsConnectorFetcher.Listener fetcherListener = new DetailsConnectorFetcher.Listener() {
+ /*
+ * Cancels details opening scroll after timeout. Avoids any unexpected
+ * scrolls via details opening.
+ */
+ private Timer disableScroller = new Timer() {
@Override
- @SuppressWarnings("boxing")
- public void fetchHasBeenScheduled(int id) {
- if (currentRow != NO_SCROLL_SCHEDULED) {
- queuedFetches.add(id);
- }
- }
-
- @Override
- @SuppressWarnings("boxing")
- public void fetchHasReturned(int id) {
- if (currentRow == NO_SCROLL_SCHEDULED
- || queuedFetches.isEmpty()) {
- return;
- }
-
- queuedFetches.remove(id);
- if (currentRow == SCROLL_TO_END_ID) {
- getWidget().scrollToEnd();
- } else {
- getWidget().scrollToRow(currentRow, destination);
- }
-
- /*
- * Schedule a deferred call whether we should stop adjusting for
- * scrolling.
- *
- * This is done deferredly just because we can't be absolutely
- * certain whether this most recent scrolling won't cascade into
- * further lazy details loading (perhaps deferredly).
- */
- scrollStopChecker.schedule();
+ public void run() {
+ targetRow = -1;
}
};
- private int currentRow = NO_SCROLL_SCHEDULED;
- private final Set<Integer> queuedFetches = new HashSet<Integer>();
- private final ScrollStopChecker scrollStopChecker = new ScrollStopChecker();
- private ScrollDestination destination;
-
- public LazyDetailsScrollAdjuster() {
- detailsConnectorFetcher.setListener(fetcherListener);
- }
+ private Integer targetRow = -1;
+ private ScrollDestination destination = null;
- public void adjustForEnd() {
- currentRow = SCROLL_TO_END_ID;
+ public void scrollToRow(Integer row, ScrollDestination dest) {
+ targetRow = row;
+ destination = dest;
+ disableScroller.schedule(DISABLE_LAZY_SCROLL_TIMEOUT);
}
- public void adjustFor(int row, ScrollDestination destination) {
- currentRow = row;
- this.destination = destination;
+ /**
+ * Inform LazyDetailsScroller that a details row has opened on a row.
+ *
+ * @param rowIndex
+ * index of row with details now open
+ */
+ public void detailsOpened(int rowIndex) {
+ if (targetRow == rowIndex) {
+ getWidget().scrollToRow(targetRow, destination);
+ disableScroller.run();
+ }
}
@Override
public boolean isWorkPending() {
- return currentRow != NO_SCROLL_SCHEDULED
- || !queuedFetches.isEmpty()
- || scrollStopChecker.isWorkPending();
+ return disableScroller.isRunning();
}
}
@@ -677,20 +570,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements
*/
private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
- private AbstractRowHandleSelectionModel<JsonObject> selectionModel;
- private Set<String> selectedKeys = new LinkedHashSet<String>();
private List<String> columnOrder = new ArrayList<String>();
/**
- * {@link #selectionUpdatedFromState} is set to true when
- * {@link #updateSelectionFromState()} makes changes to selection. This flag
- * tells the {@code internalSelectionChangeHandler} to not send same data
- * straight back to server. Said listener sets it back to false when
- * handling that event.
- */
- private boolean selectionUpdatedFromState;
-
- /**
* {@link #columnsUpdatedFromState} is set to true when
* {@link #updateColumnOrderFromState(List)} is updating the column order
* for the widget. This flag tells the {@link #columnReorderHandler} to not
@@ -701,68 +583,57 @@ public class GridConnector extends AbstractHasComponentsConnector implements
private RpcDataSource dataSource;
- private SelectionHandler<JsonObject> internalSelectionChangeHandler = new SelectionHandler<JsonObject>() {
- @Override
- public void onSelect(SelectionEvent<JsonObject> event) {
- if (event.isBatchedSelection()) {
- return;
- }
- if (!selectionUpdatedFromState) {
- for (JsonObject row : event.getRemoved()) {
- selectedKeys.remove(dataSource.getRowKey(row));
- }
-
- for (JsonObject row : event.getAdded()) {
- selectedKeys.add(dataSource.getRowKey(row));
- }
-
- getRpcProxy(GridServerRpc.class).select(
- new ArrayList<String>(selectedKeys));
- } else {
- selectionUpdatedFromState = false;
- }
- }
- };
+ /* Used to track Grid editor columns with validation errors */
+ private final Map<Column<?, JsonObject>, String> columnToErrorMessage = new HashMap<Column<?, JsonObject>, String>();
private ItemClickHandler itemClickHandler = new ItemClickHandler();
private String lastKnownTheme = null;
private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator();
-
- private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher(
- getRpcProxy(GridServerRpc.class));
+ private final CustomStyleGenerator styleGenerator = new CustomStyleGenerator();
private final DetailsListener detailsListener = new DetailsListener() {
@Override
public void reapplyDetailsVisibility(final int rowIndex,
final JsonObject row) {
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- if (hasDetailsOpen(row)) {
- getWidget().setDetailsVisible(rowIndex, true);
- detailsConnectorFetcher.schedule();
- } else {
+ if (hasDetailsOpen(row)) {
+ // Command for opening details row.
+ ScheduledCommand openDetails = new ScheduledCommand() {
+ @Override
+ public void execute() {
+ // Re-apply to force redraw.
getWidget().setDetailsVisible(rowIndex, false);
+ getWidget().setDetailsVisible(rowIndex, true);
+ lazyDetailsScroller.detailsOpened(rowIndex);
}
+ };
+
+ if (initialChange) {
+ Scheduler.get().scheduleDeferred(openDetails);
+ } else {
+ Scheduler.get().scheduleFinally(openDetails);
}
- });
+ } else {
+ getWidget().setDetailsVisible(rowIndex, false);
+ }
}
private boolean hasDetailsOpen(JsonObject row) {
return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
- && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE);
- }
-
- @Override
- public void closeDetails(int rowIndex) {
- getWidget().setDetailsVisible(rowIndex, false);
+ && row.getString(GridState.JSONKEY_DETAILS_VISIBLE) != null;
}
};
- private final LazyDetailsScrollAdjuster lazyDetailsScrollAdjuster = new LazyDetailsScrollAdjuster();
+ private final LazyDetailsScroller lazyDetailsScroller = new LazyDetailsScroller();
+ private final CustomEditorHandler editorHandler = new CustomEditorHandler();
+
+ /*
+ * Initially details need to behave a bit differently to allow some
+ * escalator magic.
+ */
+ private boolean initialChange;
@Override
@SuppressWarnings("unchecked")
@@ -797,11 +668,13 @@ public class GridConnector extends AbstractHasComponentsConnector implements
@Override
public void scrollToEnd() {
- lazyDetailsScrollAdjuster.adjustForEnd();
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
getWidget().scrollToEnd();
+ // Scrolls further if details opens.
+ lazyDetailsScroller.scrollToRow(dataSource.size() - 1,
+ ScrollDestination.END);
}
});
}
@@ -809,11 +682,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements
@Override
public void scrollToRow(final int row,
final ScrollDestination destination) {
- lazyDetailsScrollAdjuster.adjustFor(row, destination);
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
getWidget().scrollToRow(row, destination);
+ // Scrolls a bit further if details opens.
+ lazyDetailsScroller.scrollToRow(row, destination);
}
});
}
@@ -822,59 +696,16 @@ public class GridConnector extends AbstractHasComponentsConnector implements
public void recalculateColumnWidths() {
getWidget().recalculateColumnWidths();
}
-
- @Override
- @SuppressWarnings("boxing")
- public void setDetailsConnectorChanges(
- Set<DetailsConnectorChange> connectorChanges, int fetchId) {
- customDetailsGenerator
- .setDetailsConnectorChanges(connectorChanges);
-
- List<DetailsConnectorChange> removedFirst = new ArrayList<DetailsConnectorChange>(
- connectorChanges);
- Collections.sort(removedFirst,
- DetailsConnectorChange.REMOVED_FIRST_COMPARATOR);
-
- // refresh moved/added details rows
- for (DetailsConnectorChange change : removedFirst) {
- Integer oldIndex = change.getOldIndex();
- Integer newIndex = change.getNewIndex();
-
- assert oldIndex == null || oldIndex >= 0 : "Got an "
- + "invalid old index: " + oldIndex
- + " (connector: " + change.getConnector() + ")";
- assert newIndex == null || newIndex >= 0 : "Got an "
- + "invalid new index: " + newIndex
- + " (connector: " + change.getConnector() + ")";
-
- if (oldIndex != null) {
- /* Close the old/removed index */
- getWidget().setDetailsVisible(oldIndex, false);
-
- if (change.isShouldStillBeVisible()) {
- getWidget().setDetailsVisible(oldIndex, true);
- }
- }
-
- if (newIndex != null) {
- /*
- * Since the component was lazy loaded, we need to
- * refresh the details by toggling it.
- */
- getWidget().setDetailsVisible(newIndex, false);
- getWidget().setDetailsVisible(newIndex, true);
- }
- }
- detailsConnectorFetcher.responseReceived(fetchId);
- }
});
- getWidget().addSelectionHandler(internalSelectionChangeHandler);
-
/* Item click events */
getWidget().addBodyClickHandler(itemClickHandler);
getWidget().addBodyDoubleClickHandler(itemClickHandler);
+ /* Style Generators */
+ getWidget().setCellStyleGenerator(styleGenerator);
+ getWidget().setRowStyleGenerator(styleGenerator);
+
getWidget().addSortHandler(new SortHandler<JsonObject>() {
@Override
public void sort(SortEvent<JsonObject> event) {
@@ -899,36 +730,51 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
});
- getWidget().addSelectAllHandler(new SelectAllHandler<JsonObject>() {
-
- @Override
- public void onSelectAll(SelectAllEvent<JsonObject> event) {
- getRpcProxy(GridServerRpc.class).selectAll();
- }
-
- });
-
- getWidget().setEditorHandler(new CustomEditorHandler());
+ getWidget().setEditorHandler(editorHandler);
getWidget().addColumnReorderHandler(columnReorderHandler);
getWidget().addColumnVisibilityChangeHandler(
columnVisibilityChangeHandler);
+
+ ConnectorFocusAndBlurHandler.addHandlers(this);
+
getWidget().setDetailsGenerator(customDetailsGenerator);
getLayoutManager().registerDependency(this, getWidget().getElement());
- layout();
- }
+ getWidget().addEditorEventHandler(new EditorEventHandler() {
+ @Override
+ public void onEditorOpen(EditorOpenEvent e) {
+ if (hasEventListener(GridConstants.EDITOR_OPEN_EVENT_ID)) {
+ String rowKey = getRowKey((JsonObject) e.getRow());
+ getRpcProxy(GridServerRpc.class).editorOpen(rowKey);
+ }
+ }
- @Override
- public void onUnregister() {
- customDetailsGenerator.indexToDetailsMap.clear();
+ @Override
+ public void onEditorMove(EditorMoveEvent e) {
+ if (hasEventListener(GridConstants.EDITOR_MOVE_EVENT_ID)) {
+ String rowKey = getRowKey((JsonObject) e.getRow());
+ getRpcProxy(GridServerRpc.class).editorMove(rowKey);
+ }
+ }
- super.onUnregister();
+ @Override
+ public void onEditorClose(EditorCloseEvent e) {
+ if (hasEventListener(GridConstants.EDITOR_CLOSE_EVENT_ID)) {
+ String rowKey = getRowKey((JsonObject) e.getRow());
+ getRpcProxy(GridServerRpc.class).editorClose(rowKey);
+ }
+ }
+ });
+
+ layout();
}
@Override
public void onStateChanged(final StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
+ initialChange = stateChangeEvent.isInitialStateChange();
+
// Column updates
if (stateChangeEvent.hasPropertyChanged("columns")) {
@@ -959,19 +805,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
updateFooterFromState(getState().footer);
}
- // Selection
- if (stateChangeEvent.hasPropertyChanged("selectionMode")) {
- onSelectionModeChange();
- updateSelectDeselectAllowed();
- } else if (stateChangeEvent
- .hasPropertyChanged("singleSelectDeselectAllowed")) {
- updateSelectDeselectAllowed();
- }
-
- if (stateChangeEvent.hasPropertyChanged("selectedKeys")) {
- updateSelectionFromState();
- }
-
// Sorting
if (stateChangeEvent.hasPropertyChanged("sortColumns")
|| stateChangeEvent.hasPropertyChanged("sortDirs")) {
@@ -998,14 +831,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
}
- private void updateSelectDeselectAllowed() {
- SelectionModel<JsonObject> model = getWidget().getSelectionModel();
- if (model instanceof SelectionModel.Single<?>) {
- ((SelectionModel.Single<?>) model)
- .setDeselectAllowed(getState().singleSelectDeselectAllowed);
- }
- }
-
private void updateColumnOrderFromState(List<String> stateColumnOrder) {
CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder
.size()];
@@ -1178,20 +1003,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
/**
- * If we have a selection column renderer, we need to offset the index by
- * one when referring to the column index in the widget.
- */
- private int getWidgetColumnIndex(final int columnIndex) {
- Renderer<Boolean> selectionColumnRenderer = getWidget()
- .getSelectionModel().getSelectionColumnRenderer();
- int widgetColumnIndex = columnIndex;
- if (selectionColumnRenderer != null) {
- widgetColumnIndex++;
- }
- return widgetColumnIndex;
- }
-
- /**
* Updates the column values from a state
*
* @param column
@@ -1253,81 +1064,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
getWidget().setDataSource(this.dataSource);
}
- private void onSelectionModeChange() {
- SharedSelectionMode mode = getState().selectionMode;
- if (mode == null) {
- getLogger().fine("ignored mode change");
- return;
- }
-
- AbstractRowHandleSelectionModel<JsonObject> model = createSelectionModel(mode);
- if (selectionModel == null
- || !model.getClass().equals(selectionModel.getClass())) {
- selectionModel = model;
- getWidget().setSelectionModel(model);
- selectedKeys.clear();
- }
- }
-
- @OnStateChange("hasCellStyleGenerator")
- private void onCellStyleGeneratorChange() {
- if (getState().hasCellStyleGenerator) {
- getWidget().setCellStyleGenerator(new CustomCellStyleGenerator());
- } else {
- getWidget().setCellStyleGenerator(null);
- }
- }
-
- @OnStateChange("hasRowStyleGenerator")
- private void onRowStyleGeneratorChange() {
- if (getState().hasRowStyleGenerator) {
- getWidget().setRowStyleGenerator(new CustomRowStyleGenerator());
- } else {
- getWidget().setRowStyleGenerator(null);
- }
- }
-
- private void updateSelectionFromState() {
- boolean changed = false;
-
- List<String> stateKeys = getState().selectedKeys;
-
- // find new deselections
- for (String key : selectedKeys) {
- if (!stateKeys.contains(key)) {
- changed = true;
- deselectByHandle(dataSource.getHandleByKey(key));
- }
- }
-
- // find new selections
- for (String key : stateKeys) {
- if (!selectedKeys.contains(key)) {
- changed = true;
- selectByHandle(dataSource.getHandleByKey(key));
- }
- }
-
- /*
- * A defensive copy in case the collection in the state is mutated
- * instead of re-assigned.
- */
- selectedKeys = new LinkedHashSet<String>(stateKeys);
-
- /*
- * We need to fire this event so that Grid is able to re-render the
- * selection changes (if applicable).
- */
- if (changed) {
- // At least for now there's no way to send the selected and/or
- // deselected row data. Some data is only stored as keys
- selectionUpdatedFromState = true;
- getWidget().fireEvent(
- new SelectionEvent<JsonObject>(getWidget(),
- (List<JsonObject>) null, null, false));
- }
- }
-
private void onSortStateChange() {
List<SortOrder> sortOrder = new ArrayList<SortOrder>();
@@ -1346,41 +1082,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
return Logger.getLogger(getClass().getName());
}
- @SuppressWarnings("static-method")
- private AbstractRowHandleSelectionModel<JsonObject> createSelectionModel(
- SharedSelectionMode mode) {
- switch (mode) {
- case SINGLE:
- return new SelectionModelSingle<JsonObject>();
- case MULTI:
- return new SelectionModelMulti<JsonObject>();
- case NONE:
- return new SelectionModelNone<JsonObject>();
- default:
- throw new IllegalStateException("unexpected mode value: " + mode);
- }
- }
-
- /**
- * A workaround method for accessing the protected method
- * {@code AbstractRowHandleSelectionModel.selectByHandle}
- */
- private native void selectByHandle(RowHandle<JsonObject> handle)
- /*-{
- var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel;
- model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle);
- }-*/;
-
- /**
- * A workaround method for accessing the protected method
- * {@code AbstractRowHandleSelectionModel.deselectByHandle}
- */
- private native void deselectByHandle(RowHandle<JsonObject> handle)
- /*-{
- var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel;
- model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle);
- }-*/;
-
/**
* Gets the row key for a row object.
*
@@ -1405,12 +1106,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements
@Override
public void updateCaption(ComponentConnector connector) {
// TODO Auto-generated method stub
-
}
@Override
public void onConnectorHierarchyChange(
ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
+ customDetailsGenerator.updateConnectorHierarchy(getChildren());
}
public String getColumnId(Grid.Column<?, ?> column) {
@@ -1427,8 +1128,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
@Override
public boolean isWorkPending() {
- return detailsConnectorFetcher.isWorkPending()
- || lazyDetailsScrollAdjuster.isWorkPending();
+ return lazyDetailsScroller.isWorkPending();
}
/**
@@ -1441,4 +1141,74 @@ public class GridConnector extends AbstractHasComponentsConnector implements
public DetailsListener getDetailsListener() {
return detailsListener;
}
+
+ @Override
+ public boolean hasTooltip() {
+ return getState().hasDescriptions || super.hasTooltip();
+ }
+
+ @Override
+ public TooltipInfo getTooltipInfo(Element element) {
+ CellReference<JsonObject> cell = getWidget().getCellReference(element);
+
+ if (cell != null) {
+ JsonObject row = cell.getRow();
+ if (row == null) {
+ return null;
+ }
+
+ Column<?, JsonObject> column = cell.getColumn();
+ if (!(column instanceof CustomGridColumn)) {
+ // Selection checkbox column
+ return null;
+ }
+ CustomGridColumn c = (CustomGridColumn) column;
+
+ JsonObject cellDescriptions = row
+ .getObject(GridState.JSONKEY_CELLDESCRIPTION);
+
+ if (cellDescriptions != null && cellDescriptions.hasKey(c.id)) {
+ return new TooltipInfo(cellDescriptions.getString(c.id));
+ } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) {
+ return new TooltipInfo(
+ row.getString(GridState.JSONKEY_ROWDESCRIPTION));
+ } else {
+ return null;
+ }
+ }
+
+ return super.getTooltipInfo(element);
+ }
+
+ /**
+ * Creates a concatenation of all columns errors for Editor.
+ *
+ * @since
+ * @return displayed error string
+ */
+ private String getColumnErrors() {
+ List<String> errors = new ArrayList<String>();
+
+ for (Grid.Column<?, JsonObject> c : getWidget().getColumns()) {
+ if (!(c instanceof CustomGridColumn)) {
+ continue;
+ }
+
+ String error = columnToErrorMessage.get(c);
+ if (error != null) {
+ errors.add(error);
+ }
+ }
+
+ String result = "";
+ Iterator<String> i = errors.iterator();
+ while (i.hasNext()) {
+ result += i.next();
+ if (i.hasNext()) {
+ result += ", ";
+ }
+ }
+ return result.isEmpty() ? null : result;
+ }
+
}
diff --git a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java
new file mode 100644
index 0000000000..e4ad50e7ac
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java
@@ -0,0 +1,383 @@
+/*
+ * 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.connectors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.data.DataSource;
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.renderers.ComplexRenderer;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.DataAvailableEvent;
+import com.vaadin.client.widget.grid.DataAvailableHandler;
+import com.vaadin.client.widget.grid.events.SelectAllEvent;
+import com.vaadin.client.widget.grid.events.SelectAllHandler;
+import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.HeaderCell;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.Range;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
+import com.vaadin.ui.Grid.MultiSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link MultiSelectionModel}.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+@Connect(MultiSelectionModel.class)
+public class MultiSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel.Multi<JsonObject>> {
+
+ private Multi<JsonObject> selectionModel = createSelectionModel();
+ private SpaceSelectHandler<JsonObject> spaceHandler;
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(selectionModel);
+ spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public void onUnregister() {
+ spaceHandler.removeHandler();
+ }
+
+ @Override
+ protected Multi<JsonObject> createSelectionModel() {
+ return new MultiSelectionModel();
+ }
+
+ @Override
+ public MultiSelectionModelState getState() {
+ return (MultiSelectionModelState) super.getState();
+ }
+
+ @OnStateChange("allSelected")
+ void updateSelectAllCheckbox() {
+ if (selectionModel.getSelectionColumnRenderer() != null) {
+ HeaderCell cell = getGrid().getDefaultHeaderRow().getCell(
+ getGrid().getColumn(0));
+ CheckBox widget = (CheckBox) cell.getWidget();
+ widget.setValue(getState().allSelected, false);
+ }
+ }
+
+ protected class MultiSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.Multi.Batched<JsonObject> {
+
+ private ComplexRenderer<Boolean> renderer = null;
+ private Set<RowHandle<JsonObject>> selected = new HashSet<RowHandle<JsonObject>>();
+ private Set<RowHandle<JsonObject>> deselected = new HashSet<RowHandle<JsonObject>>();
+ private HandlerRegistration selectAll;
+ private HandlerRegistration dataAvailable;
+ private Range availableRows;
+ private boolean batchSelect = false;
+
+ @Override
+ public void setGrid(Grid<JsonObject> grid) {
+ super.setGrid(grid);
+ if (grid != null) {
+ renderer = createSelectionColumnRenderer(grid);
+ selectAll = getGrid().addSelectAllHandler(
+ new SelectAllHandler<JsonObject>() {
+
+ @Override
+ public void onSelectAll(
+ SelectAllEvent<JsonObject> event) {
+ selectAll();
+ }
+ });
+ dataAvailable = getGrid().addDataAvailableHandler(
+ new DataAvailableHandler() {
+
+ @Override
+ public void onDataAvailable(DataAvailableEvent event) {
+ availableRows = event.getAvailableRows();
+ }
+ });
+ } else if (renderer != null) {
+ renderer.destroy();
+ selectAll.removeHandler();
+ dataAvailable.removeHandler();
+ renderer = null;
+ }
+ }
+
+ /**
+ * Creates a selection column renderer. This method can be overridden to
+ * use a custom renderer or use {@code null} to disable the selection
+ * column.
+ *
+ * @param grid
+ * the grid for this selection model
+ * @return selection column renderer or {@code null} if not needed
+ */
+ protected ComplexRenderer<Boolean> createSelectionColumnRenderer(
+ Grid<JsonObject> grid) {
+ return new MultiSelectionRenderer<JsonObject>(grid);
+ }
+
+ /**
+ * Selects all available rows, sends request to server to select
+ * everything.
+ */
+ public void selectAll() {
+ assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection.";
+
+ DataSource<JsonObject> dataSource = getGrid().getDataSource();
+ for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) {
+ final JsonObject row = dataSource.getRow(i);
+ if (row != null) {
+ RowHandle<JsonObject> handle = dataSource.getHandle(row);
+ markAsSelected(handle, true);
+ }
+ }
+
+ getRpcProxy(MultiSelectionModelServerRpc.class).selectAll();
+ }
+
+ @Override
+ public Renderer<Boolean> getSelectionColumnRenderer() {
+ return renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean select(JsonObject... rows) {
+ return select(Arrays.asList(rows));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean deselect(JsonObject... rows) {
+ return deselect(Arrays.asList(rows));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always {@code true}
+ */
+ @Override
+ public boolean deselectAll() {
+ assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection.";
+
+ DataSource<JsonObject> dataSource = getGrid().getDataSource();
+ for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) {
+ final JsonObject row = dataSource.getRow(i);
+ if (row != null) {
+ RowHandle<JsonObject> handle = dataSource.getHandle(row);
+ markAsSelected(handle, false);
+ }
+ }
+
+ getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll();
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean select(Collection<JsonObject> rows) {
+ if (rows.isEmpty()) {
+ return false;
+ }
+
+ for (JsonObject row : rows) {
+ RowHandle<JsonObject> rowHandle = getRowHandle(row);
+ if (markAsSelected(rowHandle, true)) {
+ selected.add(rowHandle);
+ }
+ }
+
+ if (!isBeingBatchSelected()) {
+ sendSelected();
+ }
+ return true;
+ }
+
+ /**
+ * Marks the given row to be selected or deselected. Returns true if the
+ * value actually changed.
+ * <p>
+ * Note: If selection model is in batch select state, the row will be
+ * pinned on select.
+ *
+ * @param row
+ * row handle
+ * @param selected
+ * {@code true} if row should be selected; {@code false} if
+ * not
+ * @return {@code true} if selected status changed; {@code false} if not
+ */
+ protected boolean markAsSelected(RowHandle<JsonObject> row,
+ boolean selected) {
+ if (selected && !isSelected(row.getRow())) {
+ row.getRow().put(GridState.JSONKEY_SELECTED, true);
+ } else if (!selected && isSelected(row.getRow())) {
+ row.getRow().remove(GridState.JSONKEY_SELECTED);
+ } else {
+ return false;
+ }
+
+ row.updateRow();
+
+ if (isBeingBatchSelected()) {
+ row.pin();
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean deselect(Collection<JsonObject> rows) {
+ if (rows.isEmpty()) {
+ return false;
+ }
+
+ for (JsonObject row : rows) {
+ RowHandle<JsonObject> rowHandle = getRowHandle(row);
+ if (markAsSelected(rowHandle, false)) {
+ deselected.add(rowHandle);
+ }
+ }
+
+ if (!isBeingBatchSelected()) {
+ sendDeselected();
+ }
+ return true;
+ }
+
+ /**
+ * Sends a deselect RPC call to server-side containing all deselected
+ * rows. Unpins any pinned rows.
+ */
+ private void sendDeselected() {
+ getRpcProxy(MultiSelectionModelServerRpc.class).deselect(
+ getRowKeys(deselected));
+
+ if (isBeingBatchSelected()) {
+ for (RowHandle<JsonObject> row : deselected) {
+ row.unpin();
+ }
+ }
+
+ deselected.clear();
+ }
+
+ /**
+ * Sends a select RPC call to server-side containing all selected rows.
+ * Unpins any pinned rows.
+ */
+ private void sendSelected() {
+ getRpcProxy(MultiSelectionModelServerRpc.class).select(
+ getRowKeys(selected));
+
+ if (isBeingBatchSelected()) {
+ for (RowHandle<JsonObject> row : selected) {
+ row.unpin();
+ }
+ }
+
+ selected.clear();
+ }
+
+ private List<String> getRowKeys(Set<RowHandle<JsonObject>> handles) {
+ List<String> keys = new ArrayList<String>();
+ for (RowHandle<JsonObject> handle : handles) {
+ keys.add(getRowKey(handle.getRow()));
+ }
+ return keys;
+ }
+
+ private Set<JsonObject> getRows(Set<RowHandle<JsonObject>> handles) {
+ Set<JsonObject> rows = new HashSet<JsonObject>();
+ for (RowHandle<JsonObject> handle : handles) {
+ rows.add(handle.getRow());
+ }
+ return rows;
+ }
+
+ @Override
+ public void startBatchSelect() {
+ assert selected.isEmpty() && deselected.isEmpty() : "Row caches were not clear.";
+ batchSelect = true;
+ }
+
+ @Override
+ public void commitBatchSelect() {
+ assert batchSelect : "Not batch selecting.";
+ if (!selected.isEmpty()) {
+ sendSelected();
+ }
+
+ if (!deselected.isEmpty()) {
+ sendDeselected();
+ }
+ batchSelect = false;
+ }
+
+ @Override
+ public boolean isBeingBatchSelected() {
+ return batchSelect;
+ }
+
+ @Override
+ public Collection<JsonObject> getSelectedRowsBatch() {
+ return Collections.unmodifiableSet(getRows(selected));
+ }
+
+ @Override
+ public Collection<JsonObject> getDeselectedRowsBatch() {
+ return Collections.unmodifiableSet(getRows(deselected));
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java
new file mode 100644
index 0000000000..b540eed6d5
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java
@@ -0,0 +1,42 @@
+/*
+ * 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.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModelNone;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.ui.Grid.NoSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link NoSelectionModel}.
+ */
+@Connect(NoSelectionModel.class)
+public class NoSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel<JsonObject>> {
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(createSelectionModel());
+ }
+
+ @Override
+ protected SelectionModel<JsonObject> createSelectionModel() {
+ return new SelectionModelNone<JsonObject>();
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
index 627ee74eca..5daa02c3bf 100644
--- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
+++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
@@ -17,6 +17,7 @@
package com.vaadin.client.connectors;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import com.vaadin.client.ServerConnector;
@@ -64,14 +65,6 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
* @see GridState#JSONKEY_DETAILS_VISIBLE
*/
void reapplyDetailsVisibility(int rowIndex, JsonObject row);
-
- /**
- * Closes details for a row.
- *
- * @param rowIndex
- * the index of the row for which to close details
- */
- void closeDetails(int rowIndex);
}
public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> {
@@ -104,15 +97,28 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
public void resetDataAndSize(int size) {
RpcDataSource.this.resetDataAndSize(size);
}
+
+ @Override
+ public void updateRowData(JsonArray rowArray) {
+ for (int i = 0; i < rowArray.length(); ++i) {
+ RpcDataSource.this.updateRowData(rowArray.getObject(i));
+ }
+ }
});
}
private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class);
private DetailsListener detailsListener;
+ private JsonArray droppedRowKeys = Json.createArray();
@Override
protected void requestRows(int firstRowIndex, int numberOfRows,
RequestRowsCallback<JsonObject> callback) {
+ if (droppedRowKeys.length() > 0) {
+ rpcProxy.dropRows(droppedRowKeys);
+ droppedRowKeys = Json.createArray();
+ }
+
/*
* If you're looking at this code because you want to learn how to
* use AbstactRemoteDataSource, please look somewhere else instead.
@@ -184,23 +190,15 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
}
@Override
- protected void pinHandle(RowHandleImpl handle) {
- // Server only knows if something is pinned or not. No need to pin
- // multiple times.
- boolean pinnedBefore = handle.isPinned();
- super.pinHandle(handle);
- if (!pinnedBefore) {
- rpcProxy.setPinned(getRowKey(handle.getRow()), true);
- }
- }
-
- @Override
protected void unpinHandle(RowHandleImpl handle) {
// Row data is no longer available after it has been unpinned.
String key = getRowKey(handle.getRow());
super.unpinHandle(handle);
if (!handle.isPinned()) {
- rpcProxy.setPinned(key, false);
+ if (indexOfKey(key) == -1) {
+ // Row out of view has been unpinned. drop it
+ droppedRowKeys.set(droppedRowKeys.length(), key);
+ }
}
}
@@ -222,9 +220,25 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
}
}
+ /**
+ * Updates row data based on row key.
+ *
+ * @since 7.6
+ * @param row
+ * new row object
+ */
+ protected void updateRowData(JsonObject row) {
+ int index = indexOfKey(getRowKey(row));
+ if (index >= 0) {
+ setRowData(index, Collections.singletonList(row));
+ }
+ }
+
@Override
- protected void onDropFromCache(int rowIndex) {
- detailsListener.closeDetails(rowIndex);
+ protected void onDropFromCache(int rowIndex, JsonObject row) {
+ if (!((RowHandleImpl) getHandle(row)).isPinned()) {
+ droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row));
+ }
}
}
diff --git a/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java
new file mode 100644
index 0000000000..7c66903c2c
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java
@@ -0,0 +1,148 @@
+/*
+ * 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.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Single;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
+import com.vaadin.ui.Grid.SingleSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link SingleSelectionModel}.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+@Connect(SingleSelectionModel.class)
+public class SingleSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel.Single<JsonObject>> {
+
+ private SpaceSelectHandler<JsonObject> spaceHandler;
+ private ClickSelectHandler<JsonObject> clickHandler;
+ private Single<JsonObject> selectionModel = createSelectionModel();
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(selectionModel);
+ spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid());
+ clickHandler = new ClickSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public SingleSelectionModelState getState() {
+ return (SingleSelectionModelState) super.getState();
+ }
+
+ @Override
+ public void onUnregister() {
+ spaceHandler.removeHandler();
+ clickHandler.removeHandler();
+
+ super.onUnregister();
+ }
+
+ @Override
+ protected Single<JsonObject> createSelectionModel() {
+ return new SingleSelectionModel();
+ }
+
+ @OnStateChange("deselectAllowed")
+ void updateDeselectAllowed() {
+ selectionModel.setDeselectAllowed(getState().deselectAllowed);
+ }
+
+ /**
+ * SingleSelectionModel without a selection column renderer.
+ */
+ public class SingleSelectionModel extends AbstractSelectionModel implements
+ SelectionModel.Single<JsonObject> {
+
+ private RowHandle<JsonObject> selectedRow;
+ private boolean deselectAllowed;
+
+ @Override
+ public Renderer<Boolean> getSelectionColumnRenderer() {
+ return null;
+ }
+
+ @Override
+ public boolean select(JsonObject row) {
+ boolean changed = false;
+ if ((row == null && isDeselectAllowed())
+ || (row != null && !getRowHandle(row).equals(selectedRow))) {
+ if (selectedRow != null) {
+ selectedRow.getRow().remove(GridState.JSONKEY_SELECTED);
+ selectedRow.updateRow();
+ selectedRow.unpin();
+ selectedRow = null;
+ changed = true;
+ }
+
+ if (row != null) {
+ selectedRow = getRowHandle(row);
+ selectedRow.pin();
+ selectedRow.getRow().put(GridState.JSONKEY_SELECTED, true);
+ selectedRow.updateRow();
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ getRpcProxy(SingleSelectionModelServerRpc.class).select(
+ getRowKey(row));
+ }
+
+ return changed;
+ }
+
+ @Override
+ public boolean deselect(JsonObject row) {
+ if (getRowHandle(row).equals(selectedRow)) {
+ select(null);
+ }
+ return false;
+ }
+
+ @Override
+ public JsonObject getSelectedRow() {
+ throw new UnsupportedOperationException(
+ "This client-side selection model "
+ + getClass().getSimpleName()
+ + " does not know selected row.");
+ }
+
+ @Override
+ public void setDeselectAllowed(boolean deselectAllowed) {
+ this.deselectAllowed = deselectAllowed;
+ }
+
+ @Override
+ public boolean isDeselectAllowed() {
+ return deselectAllowed;
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
index 459127c9b4..58cd5c5f19 100644
--- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
+++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java
@@ -16,8 +16,6 @@
package com.vaadin.client.data;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -120,12 +118,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
@Override
public T getRow() throws IllegalStateException {
- if (isPinned()) {
- return row;
- } else {
- throw new IllegalStateException("The row handle for key " + key
- + " was not pinned");
- }
+ return row;
}
public boolean isPinned() {
@@ -197,7 +190,6 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
private Map<Object, Integer> pinnedCounts = new HashMap<Object, Integer>();
private Map<Object, RowHandleImpl> pinnedRows = new HashMap<Object, RowHandleImpl>();
- protected Collection<T> temporarilyPinnedRows = Collections.emptySet();
// Size not yet known
private int size = -1;
@@ -287,8 +279,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
* Simple case: no overlap between cached data and needed data.
* Clear the cache and request new data
*/
- indexToRowMap.clear();
- keyToIndexMap.clear();
+ dropFromCache(cached);
cached = Range.between(0, 0);
handleMissingRows(getMaxCacheRange());
@@ -330,25 +321,48 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
private void dropFromCache(Range range) {
for (int i = range.getStart(); i < range.getEnd(); i++) {
+ // Called after dropping from cache. Dropped row is passed as a
+ // parameter, but is no longer present in the DataSource
T removed = indexToRowMap.remove(Integer.valueOf(i));
+ onDropFromCache(i, removed);
keyToIndexMap.remove(getRowKey(removed));
-
- onDropFromCache(i);
}
}
/**
- * A hook that can be overridden to do something whenever a row is dropped
- * from the cache.
+ * A hook that can be overridden to do something whenever a row has been
+ * dropped from the cache. DataSource no longer has anything in the given
+ * index.
+ * <p>
+ * NOTE: This method has been replaced. Override
+ * {@link #onDropFromCache(int, Object)} instead of this method.
*
* @since 7.5.0
* @param rowIndex
* the index of the dropped row
+ * @deprecated replaced by {@link #onDropFromCache(int, Object)}
*/
+ @Deprecated
protected void onDropFromCache(int rowIndex) {
// noop
}
+ /**
+ * A hook that can be overridden to do something whenever a row has been
+ * dropped from the cache. DataSource no longer has anything in the given
+ * index.
+ *
+ * @since 7.6
+ * @param rowIndex
+ * the index of the dropped row
+ * @param removed
+ * the removed row object
+ */
+ protected void onDropFromCache(int rowIndex, T removed) {
+ // Call old version as a fallback (someone might have used it)
+ onDropFromCache(rowIndex);
+ }
+
private void handleMissingRows(Range range) {
if (range.isEmpty()) {
return;
@@ -479,6 +493,15 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
* updated before the widget settings. Support for this will be
* implemented later on.
*/
+
+ // Run a dummy drop from cache for unused rows.
+ for (int i = 0; i < partition[0].length(); ++i) {
+ onDropFromCache(i + partition[0].getStart(), rowData.get(i));
+ }
+
+ for (int i = 0; i < partition[2].length(); ++i) {
+ onDropFromCache(i + partition[2].getStart(), rowData.get(i));
+ }
}
// Eventually check whether all needed rows are now available
@@ -732,4 +755,12 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
dataChangeHandler.resetDataAndSize(newSize);
}
}
+
+ protected int indexOfKey(Object rowKey) {
+ if (!keyToIndexMap.containsKey(rowKey)) {
+ return -1;
+ } else {
+ return keyToIndexMap.get(rowKey);
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/debug/internal/VDebugWindow.java b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
index b543c23e4d..fbc838f861 100644
--- a/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
+++ b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
@@ -19,6 +19,8 @@ import java.util.ArrayList;
import java.util.Date;
import com.google.gwt.core.client.Duration;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
@@ -698,22 +700,32 @@ public final class VDebugWindow extends VOverlay {
public void init() {
show();
- readStoredState();
- Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() {
+ /*
+ * Finalize initialization when all entry points have had the chance to
+ * e.g. register new sections.
+ */
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ readStoredState();
- Timer t = new Timer() {
- @Override
- public void run() {
- applyPositionAndSize();
- }
- };
+ Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() {
- @Override
- public void onResize(ResizeEvent event) {
- t.cancel();
- // TODO less
- t.schedule(1000);
+ Timer t = new Timer() {
+ @Override
+ public void run() {
+ applyPositionAndSize();
+ }
+ };
+
+ @Override
+ public void onResize(ResizeEvent event) {
+ t.cancel();
+ // TODO less
+ t.schedule(1000);
+ }
+ });
}
});
}
diff --git a/client/src/com/vaadin/client/extensions/ResponsiveConnector.java b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java
index 621c69788c..ae330be8f4 100644
--- a/client/src/com/vaadin/client/extensions/ResponsiveConnector.java
+++ b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java
@@ -205,7 +205,7 @@ public class ResponsiveConnector extends AbstractExtensionConnector implements
// Get all the rulesets from the stylesheet
var theRules = new Array();
- var IE = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()();
+ var IEOrEdge = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()() || @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isEdge()();
var IE8 = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE8()();
try {
@@ -263,8 +263,8 @@ public class ResponsiveConnector extends AbstractExtensionConnector implements
// Array of all of the separate selectors in this ruleset
var haystack = rule.selectorText.split(",");
- // IE parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both
- var selectorRegEx = IE ? /\[.*\]([\.|#]\S+)/ : /([\.|#]\S+?)\[.*\]/;
+ // IE/Edge parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both
+ var selectorRegEx = IEOrEdge ? /\[.*\]([\.|#]\S+)/ : /([\.|#]\S+?)\[.*\]/;
// Loop all the selectors in this ruleset
for(var k = 0, len2 = haystack.length; k < len2; k++) {
diff --git a/client/src/com/vaadin/client/ui/AbstractConnector.java b/client/src/com/vaadin/client/ui/AbstractConnector.java
index a20c3463c2..7f8d6c2b14 100644
--- a/client/src/com/vaadin/client/ui/AbstractConnector.java
+++ b/client/src/com/vaadin/client/ui/AbstractConnector.java
@@ -25,7 +25,7 @@ import java.util.Set;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
-import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HandlerRegistration;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.FastStringMap;
import com.vaadin.client.FastStringSet;
diff --git a/client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java b/client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java
new file mode 100644
index 0000000000..8272f99d25
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java
@@ -0,0 +1,87 @@
+/*
+ * 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.ui;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.EventHelper;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
+
+/**
+ * A handler for focus and blur events which uses {@link FocusAndBlurServerRpc}
+ * to transmit received events to the server. Events are only handled if there
+ * is a corresponding listener on the server side.
+ *
+ * @since 7.6
+ * @author Vaadin Ltd
+ */
+public class ConnectorFocusAndBlurHandler implements StateChangeHandler,
+ FocusHandler, BlurHandler {
+
+ private final AbstractComponentConnector connector;
+ private final Widget widget;
+ private HandlerRegistration focusRegistration = null;
+ private HandlerRegistration blurRegistration = null;
+
+ public static void addHandlers(AbstractComponentConnector connector) {
+ addHandlers(connector, connector.getWidget());
+ }
+
+ public static void addHandlers(AbstractComponentConnector connector,
+ Widget widget) {
+ connector.addStateChangeHandler("registeredEventListeners",
+ new ConnectorFocusAndBlurHandler(connector, widget));
+ }
+
+ private ConnectorFocusAndBlurHandler(AbstractComponentConnector connector,
+ Widget widget) {
+ this.connector = connector;
+ this.widget = widget;
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ focusRegistration = EventHelper.updateHandler(connector, this,
+ EventId.FOCUS, focusRegistration, FocusEvent.getType(), widget);
+ blurRegistration = EventHelper.updateHandler(connector, this,
+ EventId.BLUR, blurRegistration, BlurEvent.getType(), widget);
+ }
+
+ @Override
+ public void onFocus(FocusEvent event) {
+ // updateHandler ensures that this is called only when
+ // there is a listener on the server side
+ getRpc().focus();
+ }
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ // updateHandler ensures that this is called only when
+ // there is a listener on the server side
+ getRpc().blur();
+ }
+
+ private FocusAndBlurServerRpc getRpc() {
+ return connector.getRpcProxy(FocusAndBlurServerRpc.class);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/FocusableScrollPanel.java b/client/src/com/vaadin/client/ui/FocusableScrollPanel.java
index 9dd9c17675..1ac5a08ccd 100644
--- a/client/src/com/vaadin/client/ui/FocusableScrollPanel.java
+++ b/client/src/com/vaadin/client/ui/FocusableScrollPanel.java
@@ -74,7 +74,7 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements
style.setPosition(Position.FIXED);
style.setTop(0, Unit.PX);
style.setLeft(0, Unit.PX);
- if (browserInfo.isIE()) {
+ if (browserInfo.isIE() || browserInfo.isEdge()) {
// for #15294: artificially hide little bit more the
// focusElement, otherwise IE will make the window to scroll
// into it when focused
diff --git a/client/src/com/vaadin/client/ui/VButton.java b/client/src/com/vaadin/client/ui/VButton.java
index bf321f7f00..2eb967c4fa 100644
--- a/client/src/com/vaadin/client/ui/VButton.java
+++ b/client/src/com/vaadin/client/ui/VButton.java
@@ -23,7 +23,6 @@ import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.FocusWidget;
@@ -94,8 +93,6 @@ public class VButton extends FocusWidget implements ClickHandler {
/** For internal use only. May be removed or replaced in the future. */
public int clickShortcut = 0;
- private HandlerRegistration focusHandlerRegistration;
- private HandlerRegistration blurHandlerRegistration;
private long lastClickTime = 0;
public VButton() {
diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java
index 7951759fa2..cf03382333 100644
--- a/client/src/com/vaadin/client/ui/VFilterSelect.java
+++ b/client/src/com/vaadin/client/ui/VFilterSelect.java
@@ -65,6 +65,7 @@ import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ComputedStyle;
import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.DeferredWorker;
import com.vaadin.client.Focusable;
import com.vaadin.client.UIDL;
import com.vaadin.client.VConsole;
@@ -90,7 +91,7 @@ import com.vaadin.shared.util.SharedUtil;
public class VFilterSelect extends Composite implements Field, KeyDownHandler,
KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable,
SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
- HandlesAriaRequired {
+ HandlesAriaRequired, DeferredWorker {
/**
* Represents a suggestion in the suggestion popup box
@@ -417,7 +418,9 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
selectPrevPage();
} else {
- selectItem(menu.getItems().get(menu.getItems().size() - 1));
+ if (!menu.getItems().isEmpty()) {
+ selectLastItem();
+ }
}
}
@@ -596,7 +599,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
+ "]");
Element menuFirstChild = menu.getElement().getFirstChildElement();
- final int naturalMenuWidth = menuFirstChild.getOffsetWidth();
+ final int naturalMenuWidth = WidgetUtil
+ .getRequiredWidth(menuFirstChild);
if (popupOuterPadding == -1) {
popupOuterPadding = WidgetUtil
@@ -608,13 +612,21 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
menuFirstChild.getStyle().setWidth(100, Unit.PCT);
}
- if (BrowserInfo.get().isIE()) {
+ if (BrowserInfo.get().isIE()
+ && BrowserInfo.get().getBrowserMajorVersion() < 11) {
+ // Must take margin,border,padding manually into account for
+ // menu element as we measure the element child and set width to
+ // the element parent
+ double naturalMenuOuterWidth = WidgetUtil
+ .getRequiredWidthDouble(menuFirstChild)
+ + getMarginBorderPaddingWidth(menu.getElement());
+
/*
* IE requires us to specify the width for the container
* element. Otherwise it will be 100% wide
*/
- int rootWidth = Math.max(desiredWidth, naturalMenuWidth)
- - popupOuterPadding;
+ double rootWidth = Math.max(desiredWidth - popupOuterPadding,
+ naturalMenuOuterWidth);
getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
}
@@ -1280,6 +1292,12 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
sinkEvents(Event.ONPASTE);
}
+ private static double getMarginBorderPaddingWidth(Element element) {
+ final ComputedStyle s = new ComputedStyle(element);
+ return s.getMarginWidth() + s.getBorderWidth() + s.getPaddingWidth();
+
+ }
+
/*
* (non-Javadoc)
*
@@ -2185,11 +2203,15 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
@Override
public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
- if ("textbox".equals(subPart)) {
+ String[] parts = subPart.split("/");
+ if ("textbox".equals(parts[0])) {
return tb.getElement();
- } else if ("button".equals(subPart)) {
+ } else if ("button".equals(parts[0])) {
return popupOpener.getElement();
- } else if ("popup".equals(subPart) && suggestionPopup.isAttached()) {
+ } else if ("popup".equals(parts[0]) && suggestionPopup.isAttached()) {
+ if (parts.length == 2) {
+ return suggestionPopup.menu.getSubPartElement(parts[1]);
+ }
return suggestionPopup.getElement();
}
return null;
@@ -2233,4 +2255,10 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
selectPopupItemWhenResponseIsReceived = Select.NONE;
}
+ @Override
+ public boolean isWorkPending() {
+ return waitingForFilteringResponse
+ || suggestionPopup.lazyPageScroller.isRunning();
+ }
+
}
diff --git a/client/src/com/vaadin/client/ui/VFormLayout.java b/client/src/com/vaadin/client/ui/VFormLayout.java
index 84a9b4f3dd..bcbb3ebf7b 100644
--- a/client/src/com/vaadin/client/ui/VFormLayout.java
+++ b/client/src/com/vaadin/client/ui/VFormLayout.java
@@ -22,7 +22,6 @@ import java.util.List;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
@@ -327,22 +326,6 @@ public class VFormLayout extends SimplePanel {
requiredFieldIndicator = null;
}
}
-
- // Workaround for IE weirdness, sometimes returns bad height in some
- // circumstances when Caption is empty. See #1444
- // IE7 bugs more often. I wonder what happens when IE8 arrives...
- // FIXME: This could be unnecessary for IE8+
- if (BrowserInfo.get().isIE()) {
- if (isEmpty) {
- setHeight("0px");
- getElement().getStyle().setOverflow(Overflow.HIDDEN);
- } else {
- setHeight("");
- getElement().getStyle().clearOverflow();
- }
-
- }
-
}
/**
diff --git a/client/src/com/vaadin/client/ui/VRichTextArea.java b/client/src/com/vaadin/client/ui/VRichTextArea.java
index 3f63f38067..cb4482f7cb 100644
--- a/client/src/com/vaadin/client/ui/VRichTextArea.java
+++ b/client/src/com/vaadin/client/ui/VRichTextArea.java
@@ -322,7 +322,7 @@ public class VRichTextArea extends Composite implements Field, KeyPressHandler,
if ("<br>".equals(result)) {
result = "";
}
- } else if (browser.isWebkit()) {
+ } else if (browser.isWebkit() || browser.isEdge()) {
if ("<br>".equals(result) || "<div><br></div>".equals(result)) {
result = "";
}
diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java
index 2724daae77..6bb8f063a6 100644
--- a/client/src/com/vaadin/client/ui/VScrollTable.java
+++ b/client/src/com/vaadin/client/ui/VScrollTable.java
@@ -1295,8 +1295,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
if (uidl.hasVariable("selected")) {
final Set<String> selectedKeys = uidl
.getStringArrayVariableAsSet("selected");
- removeUnselectedRowKeys(selectedKeys);
-
+ // Do not update focus if there is a single selected row
+ // that is the same as the previous selection. This prevents
+ // unwanted scrolling (#18247).
+ boolean rowsUnSelected = removeUnselectedRowKeys(selectedKeys);
+ boolean updateFocus = rowsUnSelected || selectedRowKeys.size() == 0
+ || focusedRow == null;
if (scrollBody != null) {
Iterator<Widget> iterator = scrollBody.iterator();
while (iterator.hasNext()) {
@@ -1313,7 +1317,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
selected = true;
keyboardSelectionOverRowFetchInProgress = true;
}
- if (selected && selectedKeys.size() == 1) {
+ if (selected && selectedKeys.size() == 1 && updateFocus) {
/*
* If a single item is selected, move focus to the
* selected row. (#10522)
@@ -1338,14 +1342,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
return keyboardSelectionOverRowFetchInProgress;
}
- private void removeUnselectedRowKeys(final Set<String> selectedKeys) {
+ private boolean removeUnselectedRowKeys(final Set<String> selectedKeys) {
List<String> unselectedKeys = new ArrayList<String>(0);
for (String key : selectedRowKeys) {
if (!selectedKeys.contains(key)) {
unselectedKeys.add(key);
}
}
- selectedRowKeys.removeAll(unselectedKeys);
+ return selectedRowKeys.removeAll(unselectedKeys);
}
/** For internal use only. May be removed or replaced in the future. */
@@ -3629,6 +3633,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
}
} else {
c.setText(caption);
+ if (BrowserInfo.get().isIE10()) {
+ // IE10 can some times define min-height to include
+ // padding when setting the text...
+ // See https://dev.vaadin.com/ticket/15169
+ WidgetUtil.forceIERedraw(c.getElement());
+ }
}
c.setSorted(false);
diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java
index ded9977f5e..e196870348 100644
--- a/client/src/com/vaadin/client/ui/VTabsheet.java
+++ b/client/src/com/vaadin/client/ui/VTabsheet.java
@@ -61,11 +61,12 @@ import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ComputedStyle;
import com.vaadin.client.Focusable;
import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.WidgetUtil;
import com.vaadin.client.VCaption;
import com.vaadin.client.VTooltip;
+import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ComponentConstants;
@@ -1227,8 +1228,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, SubPartAware
public void updateContentNodeHeight() {
if (!isDynamicHeight()) {
int contentHeight = getOffsetHeight();
- contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
+ contentHeight -= deco.getOffsetHeight();
contentHeight -= tb.getOffsetHeight();
+
+ ComputedStyle cs = new ComputedStyle(contentNode);
+ contentHeight -= Math.ceil(cs.getPaddingHeight());
+ contentHeight -= Math.ceil(cs.getBorderHeight());
+
if (contentHeight < 0) {
contentHeight = 0;
}
diff --git a/client/src/com/vaadin/client/ui/VTextArea.java b/client/src/com/vaadin/client/ui/VTextArea.java
index 50930f2fee..bb3d3a476b 100644
--- a/client/src/com/vaadin/client/ui/VTextArea.java
+++ b/client/src/com/vaadin/client/ui/VTextArea.java
@@ -224,16 +224,16 @@ public class VTextArea extends VTextField implements DragImageModifier {
protected boolean browserSupportsMaxLengthAttribute() {
BrowserInfo info = BrowserInfo.get();
- if (info.isFirefox() && info.isBrowserVersionNewerOrEqual(4, 0)) {
+ if (info.isFirefox()) {
return true;
}
- if (info.isSafari() && info.isBrowserVersionNewerOrEqual(5, 0)) {
+ if (info.isSafari()) {
return true;
}
- if (info.isIE() && info.isBrowserVersionNewerOrEqual(10, 0)) {
+ if (info.isIE10() || info.isIE11() || info.isEdge()) {
return true;
}
- if (info.isAndroid() && info.isBrowserVersionNewerOrEqual(2, 3)) {
+ if (info.isAndroid()) {
return true;
}
return false;
diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java
index 8729de4a43..846b16d0cb 100644
--- a/client/src/com/vaadin/client/ui/VTree.java
+++ b/client/src/com/vaadin/client/ui/VTree.java
@@ -179,7 +179,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
@Override
public void execute() {
- Util.notifyParentOfSizeChange(VTree.this, true);
+ doLayout();
}
});
@@ -969,7 +969,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
open = state;
if (!rendering) {
- Util.notifyParentOfSizeChange(VTree.this, false);
+ doLayout();
}
}
@@ -2239,4 +2239,15 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
com.google.gwt.user.client.Element captionElement) {
AriaHelper.bindCaption(body, captionElement);
}
+
+ /**
+ * Tell LayoutManager that a layout is needed later for this VTree
+ */
+ private void doLayout() {
+ // IE8 needs a hack to measure the tree again after update
+ WidgetUtil.forceIE8Redraw(getElement());
+
+ // This calls LayoutManager setNeedsMeasure and layoutNow
+ Util.notifyParentOfSizeChange(this, false);
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java
index 0c1b83ab0f..963d83a6e6 100644
--- a/client/src/com/vaadin/client/ui/VUI.java
+++ b/client/src/com/vaadin/client/ui/VUI.java
@@ -18,7 +18,6 @@ package com.vaadin.client.ui;
import java.util.ArrayList;
-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.event.dom.client.HasScrollHandlers;
@@ -44,8 +43,8 @@ import com.vaadin.client.ConnectorMap;
import com.vaadin.client.Focusable;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.Profiler;
-import com.vaadin.client.WidgetUtil;
import com.vaadin.client.VConsole;
+import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
import com.vaadin.client.ui.ui.UIConnector;
@@ -515,13 +514,6 @@ public class VUI extends SimplePanel implements ResizeHandler,
public void focusStoredElement() {
if (storedFocus != null) {
storedFocus.focus();
-
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- storedFocus.focus();
- }
- });
}
}
diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java
index c5cab8ff6b..2d2d6ecee1 100644
--- a/client/src/com/vaadin/client/ui/VWindow.java
+++ b/client/src/com/vaadin/client/ui/VWindow.java
@@ -44,8 +44,6 @@ import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.shared.HandlerRegistration;
@@ -79,8 +77,7 @@ import com.vaadin.shared.ui.window.WindowRole;
* @author Vaadin Ltd
*/
public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
- ScrollHandler, KeyDownHandler, KeyUpHandler, FocusHandler, BlurHandler,
- Focusable {
+ ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
@@ -221,7 +218,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
constructDOM();
contentPanel.addScrollHandler(this);
contentPanel.addKeyDownHandler(this);
- contentPanel.addKeyUpHandler(this);
contentPanel.addFocusHandler(this);
contentPanel.addBlurHandler(this);
}
@@ -562,17 +558,10 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
private static void focusTopmostModalWindow() {
- // If we call focus() directly without scheduling, it does not work in
- // IE and FF.
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- VWindow topmost = getTopmostWindow();
- if ((topmost != null) && (topmost.vaadinModality)) {
- topmost.focus();
- }
- }
- });
+ VWindow topmost = getTopmostWindow();
+ if ((topmost != null) && (topmost.vaadinModality)) {
+ topmost.focus();
+ }
}
@Override
@@ -762,11 +751,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
modalityCurtain.removeFromParent();
- if (BrowserInfo.get().isIE()) {
- // IE leaks memory in certain cases unless we release the reference
- // (#9197)
- modalityCurtain = null;
- }
+ // IE leaks memory in certain cases unless we release the reference
+ // (#9197)
+ modalityCurtain = null;
}
/*
@@ -1353,13 +1340,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
@Override
- public void onKeyUp(KeyUpEvent event) {
- if (isClosable() && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
- onCloseClick();
- }
- }
-
- @Override
public void onBlur(BlurEvent event) {
if (client.hasEventListeners(this, EventId.BLUR)) {
client.updateVariable(id, EventId.BLUR, "", true);
@@ -1375,7 +1355,11 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
@Override
public void focus() {
- contentPanel.focus();
+ // We don't want to use contentPanel.focus() as that will use a timer in
+ // Chrome/Safari and ultimately run focus events in the wrong order when
+ // opening a modal window and focusing some other component at the same
+ // time
+ contentPanel.getElement().focus();
}
private int getDecorationHeight() {
diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java
index 2d13d62a91..2c2006e19b 100644
--- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java
+++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java
@@ -16,24 +16,17 @@
package com.vaadin.client.ui.button;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
-import com.vaadin.client.EventHelper;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.VCaption;
import com.vaadin.client.annotations.OnStateChange;
-import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
import com.vaadin.client.ui.Icon;
import com.vaadin.client.ui.VButton;
import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.button.ButtonServerRpc;
@@ -42,10 +35,7 @@ import com.vaadin.ui.Button;
@Connect(value = Button.class, loadStyle = LoadStyle.EAGER)
public class ButtonConnector extends AbstractComponentConnector implements
- BlurHandler, FocusHandler, ClickHandler {
-
- private HandlerRegistration focusHandlerRegistration = null;
- private HandlerRegistration blurHandlerRegistration = null;
+ ClickHandler {
@Override
public boolean delegateCaptionHandling() {
@@ -57,6 +47,7 @@ public class ButtonConnector extends AbstractComponentConnector implements
super.init();
getWidget().addClickHandler(this);
getWidget().client = getConnection();
+ ConnectorFocusAndBlurHandler.addHandlers(this);
}
@OnStateChange("errorMessage")
@@ -90,15 +81,6 @@ public class ButtonConnector extends AbstractComponentConnector implements
}
}
- @Override
- public void onStateChanged(StateChangeEvent stateChangeEvent) {
- super.onStateChanged(stateChangeEvent);
- focusHandlerRegistration = EventHelper.updateFocusHandler(this,
- focusHandlerRegistration);
- blurHandlerRegistration = EventHelper.updateBlurHandler(this,
- blurHandlerRegistration);
- }
-
@OnStateChange({ "caption", "captionAsHtml" })
void setCaption() {
VCaption.setCaptionText(getWidget().captionElement, getState());
@@ -127,20 +109,6 @@ public class ButtonConnector extends AbstractComponentConnector implements
}
@Override
- public void onFocus(FocusEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).focus();
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).blur();
- }
-
- @Override
public void onClick(ClickEvent event) {
if (getState().disableOnClick) {
// Simulate getting disabled from the server without waiting for the
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
index 1a54fe0454..55834397d3 100644
--- a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
@@ -268,8 +268,13 @@ public class DateCellDayEvent extends FocusableHTML implements
}
int endX = event.getClientX();
int endY = event.getClientY();
- int xDiff = startX - endX;
- int yDiff = startY - endY;
+ int xDiff = 0, yDiff = 0;
+ if (startX != -1 && startY != -1) {
+ // Drag started
+ xDiff = startX - endX;
+ yDiff = startY - endY;
+ }
+
startX = -1;
startY = -1;
mouseMoveStarted = false;
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
index 3bf6930933..158241337b 100644
--- a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
@@ -392,8 +392,11 @@ public class SimpleDayCell extends FocusableFlowPanel implements
int endX = event.getClientX();
int endY = event.getClientY();
- int xDiff = startX - endX;
- int yDiff = startY - endY;
+ int xDiff = 0, yDiff = 0;
+ if (startX != -1 && startY != -1) {
+ xDiff = startX - endX;
+ yDiff = startY - endY;
+ }
startX = -1;
startY = -1;
prevDayDiff = 0;
diff --git a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
index 9e689f3314..8cfcf7feb1 100644
--- a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
+++ b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
@@ -16,25 +16,19 @@
package com.vaadin.client.ui.checkbox;
import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
-import com.vaadin.client.EventHelper;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.VCaption;
import com.vaadin.client.VTooltip;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
import com.vaadin.client.ui.Icon;
import com.vaadin.client.ui.VCheckBox;
import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.checkbox.CheckBoxServerRpc;
import com.vaadin.shared.ui.checkbox.CheckBoxState;
@@ -42,10 +36,7 @@ import com.vaadin.ui.CheckBox;
@Connect(CheckBox.class)
public class CheckBoxConnector extends AbstractFieldConnector implements
- FocusHandler, BlurHandler, ClickHandler {
-
- private HandlerRegistration focusHandlerRegistration;
- private HandlerRegistration blurHandlerRegistration;
+ ClickHandler {
@Override
public boolean delegateCaptionHandling() {
@@ -55,21 +46,18 @@ public class CheckBoxConnector extends AbstractFieldConnector implements
@Override
protected void init() {
super.init();
+
getWidget().addClickHandler(this);
getWidget().client = getConnection();
getWidget().id = getConnectorId();
+ ConnectorFocusAndBlurHandler.addHandlers(this);
}
@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
- focusHandlerRegistration = EventHelper.updateFocusHandler(this,
- focusHandlerRegistration);
- blurHandlerRegistration = EventHelper.updateBlurHandler(this,
- blurHandlerRegistration);
-
if (null != getState().errorMessage) {
getWidget().setAriaInvalid(true);
@@ -127,20 +115,6 @@ public class CheckBoxConnector extends AbstractFieldConnector implements
}
@Override
- public void onFocus(FocusEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).focus();
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).blur();
- }
-
- @Override
public void onClick(ClickEvent event) {
if (!isEnabled()) {
return;
diff --git a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java
index 2aae9beae6..65d4a1eb9b 100644
--- a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java
+++ b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java
@@ -15,30 +15,20 @@
*/
package com.vaadin.client.ui.nativebutton;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
-import com.vaadin.client.EventHelper;
import com.vaadin.client.VCaption;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
import com.vaadin.client.ui.Icon;
import com.vaadin.client.ui.VNativeButton;
-import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.button.ButtonServerRpc;
import com.vaadin.shared.ui.button.NativeButtonState;
import com.vaadin.ui.NativeButton;
@Connect(NativeButton.class)
-public class NativeButtonConnector extends AbstractComponentConnector implements
- BlurHandler, FocusHandler {
-
- private HandlerRegistration focusHandlerRegistration;
- private HandlerRegistration blurHandlerRegistration;
+public class NativeButtonConnector extends AbstractComponentConnector {
@Override
public void init() {
@@ -47,6 +37,8 @@ public class NativeButtonConnector extends AbstractComponentConnector implements
getWidget().buttonRpcProxy = getRpcProxy(ButtonServerRpc.class);
getWidget().client = getConnection();
getWidget().paintableId = getConnectorId();
+
+ ConnectorFocusAndBlurHandler.addHandlers(this);
}
@Override
@@ -59,10 +51,6 @@ public class NativeButtonConnector extends AbstractComponentConnector implements
super.onStateChanged(stateChangeEvent);
getWidget().disableOnClick = getState().disableOnClick;
- focusHandlerRegistration = EventHelper.updateFocusHandler(this,
- focusHandlerRegistration);
- blurHandlerRegistration = EventHelper.updateBlurHandler(this,
- blurHandlerRegistration);
// Set text
VCaption.setCaptionText(getWidget(), getState());
@@ -107,19 +95,4 @@ public class NativeButtonConnector extends AbstractComponentConnector implements
public NativeButtonState getState() {
return (NativeButtonState) super.getState();
}
-
- @Override
- public void onFocus(FocusEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).focus();
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).blur();
- }
-
}
diff --git a/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java b/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java
index 938903da9a..d6ff2015b4 100644
--- a/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java
+++ b/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java
@@ -16,55 +16,23 @@
package com.vaadin.client.ui.nativeselect;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.vaadin.client.EventHelper;
-import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
import com.vaadin.client.ui.VNativeSelect;
import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
-import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.ui.NativeSelect;
@Connect(NativeSelect.class)
-public class NativeSelectConnector extends OptionGroupBaseConnector implements
- BlurHandler, FocusHandler {
+public class NativeSelectConnector extends OptionGroupBaseConnector {
- private HandlerRegistration focusHandlerRegistration = null;
- private HandlerRegistration blurHandlerRegistration = null;
-
- public NativeSelectConnector() {
- super();
- }
-
- @OnStateChange("registeredEventListeners")
- private void onServerEventListenerChanged() {
- focusHandlerRegistration = EventHelper.updateFocusHandler(this,
- focusHandlerRegistration, getWidget().getSelect());
- blurHandlerRegistration = EventHelper.updateBlurHandler(this,
- blurHandlerRegistration, getWidget().getSelect());
+ @Override
+ protected void init() {
+ super.init();
+ ConnectorFocusAndBlurHandler.addHandlers(this, getWidget().getSelect());
}
@Override
public VNativeSelect getWidget() {
return (VNativeSelect) super.getWidget();
}
-
- @Override
- public void onFocus(FocusEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).focus();
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- // EventHelper.updateFocusHandler ensures that this is called only when
- // there is a listener on server side
- getRpcProxy(FocusAndBlurServerRpc.class).blur();
- }
-
}
diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
index fc3e6ca0fc..23091d0ad5 100644
--- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java
+++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
@@ -27,8 +27,8 @@ import com.vaadin.client.BrowserInfo;
import com.vaadin.client.Paintable;
import com.vaadin.client.TooltipInfo;
import com.vaadin.client.UIDL;
-import com.vaadin.client.WidgetUtil;
import com.vaadin.client.VConsole;
+import com.vaadin.client.WidgetUtil;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.VTree;
@@ -62,6 +62,10 @@ public class TreeConnector extends AbstractComponentConnector implements
if (uidl.hasAttribute("partialUpdate")) {
handleUpdate(uidl);
+
+ // IE8 needs a hack to measure the tree again after update
+ WidgetUtil.forceIE8Redraw(getWidget().getElement());
+
getWidget().rendering = false;
return;
}
diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index d67c953c6e..f5656dfdc4 100644
--- a/client/src/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java
@@ -59,6 +59,7 @@ 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.Util;
import com.vaadin.client.VConsole;
import com.vaadin.client.ValueMap;
import com.vaadin.client.annotations.OnStateChange;
@@ -71,6 +72,7 @@ import com.vaadin.client.ui.ShortcutActionHandler;
import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.VUI;
+import com.vaadin.client.ui.VWindow;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.client.ui.window.WindowConnector;
import com.vaadin.server.Page.Styles;
@@ -319,19 +321,19 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
Scheduler.get().scheduleDeferred(new Command() {
@Override
public void execute() {
- ComponentConnector paintable = (ComponentConnector) uidl
+ ComponentConnector connector = (ComponentConnector) uidl
.getPaintableAttribute("focused", getConnection());
- if (paintable == null) {
+ if (connector == null) {
// Do not try to focus invisible components which not
// present in UIDL
return;
}
- final Widget toBeFocused = paintable.getWidget();
+ final Widget toBeFocused = connector.getWidget();
/*
* Two types of Widgets can be focused, either implementing
- * GWT HasFocus of a thinner Vaadin specific Focusable
+ * GWT Focusable of a thinner Vaadin specific Focusable
* interface.
*/
if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
@@ -340,7 +342,14 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
} else if (toBeFocused instanceof Focusable) {
((Focusable) toBeFocused).focus();
} else {
- VConsole.log("Could not focus component");
+ getLogger()
+ .severe("Server is trying to set focus to the widget of connector "
+ + Util.getConnectorString(connector)
+ + " but it is not focusable. The widget should implement either "
+ + com.google.gwt.user.client.ui.Focusable.class
+ .getName()
+ + " or "
+ + Focusable.class.getName());
}
}
});
@@ -669,6 +678,19 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
if (c instanceof WindowConnector) {
WindowConnector wc = (WindowConnector) c;
wc.setWindowOrderAndPosition();
+ VWindow window = wc.getWidget();
+ if (!window.isAttached()) {
+
+ // Attach so that all widgets inside the Window are attached
+ // when their onStateChange is run
+
+ // Made invisible here for legacy reasons and made visible
+ // at the end of stateChange. This dance could probably be
+ // removed
+ window.setVisible(false);
+ window.show();
+ }
+
}
}
@@ -752,8 +774,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
getState().pushConfiguration.mode.isEnabled());
}
if (stateChangeEvent.hasPropertyChanged("reconnectDialogConfiguration")) {
- getConnection().getConnectionStateHandler()
- .configurationUpdated();
+ getConnection().getConnectionStateHandler().configurationUpdated();
}
if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) {
@@ -1022,59 +1043,16 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}
- forceStateChangeRecursively(UIConnector.this);
- // UIDL has no stored URL which we can repaint so we do some find and
- // replace magic...
- String newThemeBase = getConnection().translateVaadinUri("theme://");
- replaceThemeAttribute(oldThemeBase, newThemeBase);
+ // Request a full resynchronization from the server to deal with legacy
+ // components
+ getConnection().getMessageSender().resynchronize();
+ // Immediately update state and do layout while waiting for the resync
+ forceStateChangeRecursively(UIConnector.this);
getLayoutManager().forceLayout();
}
/**
- * Finds all attributes where theme:// urls have possibly been used and
- * replaces any old theme url with a new one
- *
- * @param oldPrefix
- * The start of the old theme URL
- * @param newPrefix
- * The start of the new theme URL
- */
- private void replaceThemeAttribute(String oldPrefix, String newPrefix) {
- // Images
- replaceThemeAttribute("src", oldPrefix, newPrefix);
- // Embedded flash
- replaceThemeAttribute("value", oldPrefix, newPrefix);
- replaceThemeAttribute("movie", oldPrefix, newPrefix);
- }
-
- /**
- * Finds any attribute of the given type where theme:// urls have possibly
- * been used and replaces any old theme url with a new one
- *
- * @param attributeName
- * The name of the attribute, e.g. "src"
- * @param oldPrefix
- * The start of the old theme URL
- * @param newPrefix
- * The start of the new theme URL
- */
- private void replaceThemeAttribute(String attributeName, String oldPrefix,
- String newPrefix) {
- // Find all "attributeName=" which start with "oldPrefix" using e.g.
- // [^src='http://oldpath']
- NodeList<Element> elements = querySelectorAll("[" + attributeName
- + "^='" + oldPrefix + "']");
- for (int i = 0; i < elements.getLength(); i++) {
- Element element = elements.getItem(i);
- element.setAttribute(
- attributeName,
- element.getAttribute(attributeName).replace(oldPrefix,
- newPrefix));
- }
- }
-
- /**
* Force a full recursive recheck of every connector's state variables.
*
* @see #forceStateChange()
diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java
index 9b710981d8..9ea3c8bb68 100644
--- a/client/src/com/vaadin/client/ui/window/WindowConnector.java
+++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java
@@ -17,6 +17,8 @@ package com.vaadin.client.ui.window;
import java.util.logging.Logger;
+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.NativeEvent;
import com.google.gwt.dom.client.Node;
@@ -354,10 +356,6 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
if (state.modal != window.vaadinModality) {
window.setVaadinModality(!window.vaadinModality);
}
- if (!window.isAttached()) {
- window.setVisible(false); // hide until possible centering
- window.show();
- }
boolean resizeable = state.resizable
&& state.windowMode == WindowMode.NORMAL;
window.setResizable(resizeable);
@@ -407,7 +405,13 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
window.centered = state.centered;
// Ensure centering before setting visible (#16486)
if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) {
- window.center();
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ getWidget().center();
+ }
+ });
}
window.setVisible(true);
diff --git a/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
new file mode 100644
index 0000000000..564b0f1a03
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java
@@ -0,0 +1,225 @@
+/*
+ * 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.widget.grid;
+
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.Event;
+import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.ui.FocusUtil;
+import com.vaadin.client.widget.grid.events.EditorMoveEvent;
+import com.vaadin.client.widget.grid.events.EditorOpenEvent;
+import com.vaadin.client.widgets.Grid.Editor;
+import com.vaadin.client.widgets.Grid.EditorDomEvent;
+
+/**
+ * The default handler for Grid editor events. Offers several overridable
+ * protected methods for easier customization.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> {
+
+ public static final int KEYCODE_OPEN = KeyCodes.KEY_ENTER;
+ public static final int KEYCODE_MOVE = KeyCodes.KEY_ENTER;
+ public static final int KEYCODE_CLOSE = KeyCodes.KEY_ESCAPE;
+
+ private double lastTouchEventTime = 0;
+ private int lastTouchEventX = -1;
+ private int lastTouchEventY = -1;
+ private int lastTouchEventRow = -1;
+
+ /**
+ * Returns whether the given event is a touch event that should open the
+ * editor.
+ *
+ * @param event
+ * the received event
+ * @return whether the event is a touch open event
+ */
+ protected boolean isTouchOpenEvent(EditorDomEvent<T> event) {
+ final Event e = event.getDomEvent();
+ final int type = e.getTypeInt();
+
+ final double now = Duration.currentTimeMillis();
+ final int currentX = WidgetUtil.getTouchOrMouseClientX(e);
+ final int currentY = WidgetUtil.getTouchOrMouseClientY(e);
+
+ final boolean validTouchOpenEvent = type == Event.ONTOUCHEND
+ && now - lastTouchEventTime < 500
+ && lastTouchEventRow == event.getCell().getRowIndex()
+ && Math.abs(lastTouchEventX - currentX) < 20
+ && Math.abs(lastTouchEventY - currentY) < 20;
+
+ if (type == Event.ONTOUCHSTART) {
+ lastTouchEventX = currentX;
+ lastTouchEventY = currentY;
+ }
+
+ if (type == Event.ONTOUCHEND) {
+ lastTouchEventTime = now;
+ lastTouchEventRow = event.getCell().getRowIndex();
+ }
+
+ return validTouchOpenEvent;
+ }
+
+ /**
+ * Returns whether the given event should open the editor. The default
+ * implementation returns true if and only if the event is a doubleclick or
+ * if it is a keydown event and the keycode is {@link #KEYCODE_OPEN}.
+ *
+ * @param event
+ * the received event
+ * @return true if the event is an open event, false otherwise
+ */
+ protected boolean isOpenEvent(EditorDomEvent<T> event) {
+ final Event e = event.getDomEvent();
+ return e.getTypeInt() == Event.ONDBLCLICK
+ || (e.getTypeInt() == Event.ONKEYDOWN && e.getKeyCode() == KEYCODE_OPEN)
+ || isTouchOpenEvent(event);
+ }
+
+ /**
+ * Opens the editor on the appropriate row if the received event is an open
+ * event. The default implementation uses
+ * {@link #isOpenEvent(EditorDomEvent) isOpenEvent}.
+ *
+ * @param event
+ * the received event
+ * @return true if this method handled the event and nothing else should be
+ * done, false otherwise
+ */
+ protected boolean handleOpenEvent(EditorDomEvent<T> event) {
+ if (isOpenEvent(event)) {
+ final EventCellReference<T> cell = event.getCell();
+
+ editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM());
+
+ // FIXME should be in editRow
+ event.getGrid().fireEvent(new EditorOpenEvent(cell));
+
+ event.getDomEvent().preventDefault();
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Moves the editor to another row if the received event is a move event.
+ * The default implementation moves the editor to the clicked row if the
+ * event is a click; otherwise, if the event is a keydown and the keycode is
+ * {@link #KEYCODE_MOVE}, moves the editor one row up or down if the shift
+ * key is pressed or not, respectively.
+ *
+ * @param event
+ * the received event
+ * @return true if this method handled the event and nothing else should be
+ * done, false otherwise
+ */
+ protected boolean handleMoveEvent(EditorDomEvent<T> event) {
+ Event e = event.getDomEvent();
+ final EventCellReference<T> cell = event.getCell();
+
+ // TODO: Move on touch events
+ if (e.getTypeInt() == Event.ONCLICK) {
+
+ editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM());
+
+ // FIXME should be in editRow
+ event.getGrid().fireEvent(new EditorMoveEvent(cell));
+
+ return true;
+ }
+
+ else if (e.getTypeInt() == Event.ONKEYDOWN
+ && e.getKeyCode() == KEYCODE_MOVE) {
+
+ editRow(event, event.getRowIndex() + (e.getShiftKey() ? -1 : +1),
+ event.getFocusedColumnIndex());
+
+ // FIXME should be in editRow
+ event.getGrid().fireEvent(new EditorMoveEvent(cell));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the given event should close the editor. The default
+ * implementation returns true if and only if the event is a keydown event
+ * and the keycode is {@link #KEYCODE_CLOSE}.
+ *
+ * @param event
+ * the received event
+ * @return true if the event is a close event, false otherwise
+ */
+ protected boolean isCloseEvent(EditorDomEvent<T> event) {
+ final Event e = event.getDomEvent();
+ return e.getTypeInt() == Event.ONKEYDOWN
+ && e.getKeyCode() == KEYCODE_CLOSE;
+ }
+
+ /**
+ * Closes the editor if the received event is a close event. The default
+ * implementation uses {@link #isCloseEvent(EditorDomEvent) isCloseEvent}.
+ *
+ * @param event
+ * the received event
+ * @return true if this method handled the event and nothing else should be
+ * done, false otherwise
+ */
+ protected boolean handleCloseEvent(EditorDomEvent<T> event) {
+ if (isCloseEvent(event)) {
+ event.getEditor().cancel();
+ FocusUtil.setFocus(event.getGrid(), true);
+ return true;
+ }
+ return false;
+ }
+
+ protected void editRow(EditorDomEvent<T> event, int rowIndex, int colIndex) {
+ int rowCount = event.getGrid().getDataSource().size();
+ // Limit rowIndex between 0 and rowCount - 1
+ rowIndex = Math.max(0, Math.min(rowCount - 1, rowIndex));
+
+ int colCount = event.getGrid().getVisibleColumns().size();
+ // Limit colIndex between 0 and colCount - 1
+ colIndex = Math.max(0, Math.min(colCount - 1, colIndex));
+
+ event.getEditor().editRow(rowIndex, colIndex);
+ }
+
+ @Override
+ public boolean handleEvent(EditorDomEvent<T> event) {
+ final Editor<T> editor = event.getEditor();
+ final boolean isBody = event.getCell().isBody();
+
+ if (event.getGrid().isEditorActive()) {
+ return (!editor.isBuffered() && isBody && handleMoveEvent(event))
+ || handleCloseEvent(event)
+ // Swallow events if editor is open and buffered (modal)
+ || editor.isBuffered();
+ } else {
+ return event.getGrid().isEnabled() && isBody
+ && handleOpenEvent(event);
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java
new file mode 100644
index 0000000000..99f59aa82a
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.widget.grid.events;
+
+import com.vaadin.client.widget.grid.CellReference;
+
+/**
+ * Event that gets fired when an open editor is closed (and not reopened
+ * elsewhere)
+ */
+public class EditorCloseEvent extends EditorEvent {
+
+ public EditorCloseEvent(CellReference<?> cell) {
+ super(cell);
+ }
+
+ @Override
+ protected void dispatch(EditorEventHandler handler) {
+ handler.onEditorClose(this);
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java
new file mode 100644
index 0000000000..eb34033197
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java
@@ -0,0 +1,108 @@
+/*
+ * 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.widget.grid.events;
+
+import com.google.gwt.event.shared.GwtEvent;
+import com.vaadin.client.widget.grid.CellReference;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.Column;
+
+/**
+ * Base class for editor events.
+ */
+public abstract class EditorEvent extends GwtEvent<EditorEventHandler> {
+ public static final Type<EditorEventHandler> TYPE = new Type<EditorEventHandler>();
+
+ private CellReference<?> cell;
+
+ protected EditorEvent(CellReference<?> cell) {
+ this.cell = cell;
+ }
+
+ @Override
+ public Type<EditorEventHandler> getAssociatedType() {
+ return TYPE;
+ }
+
+ /**
+ * Get a reference to the Grid that fired this Event.
+ *
+ * @return a Grid reference
+ */
+ @SuppressWarnings("unchecked")
+ public <T> Grid<T> getGrid() {
+ return (Grid<T>) cell.getGrid();
+ }
+
+ /**
+ * Get a reference to the cell that was active when this Event was fired.
+ * NOTE: do <i>NOT</i> rely on this information remaining accurate after
+ * leaving the event handler.
+ *
+ * @return a cell reference
+ */
+ @SuppressWarnings("unchecked")
+ public <T> CellReference<T> getCell() {
+ return (CellReference<T>) cell;
+ }
+
+ /**
+ * Get a reference to the row that was active when this Event was fired.
+ * NOTE: do <i>NOT</i> rely on this information remaining accurate after
+ * leaving the event handler.
+ *
+ * @return a row data object
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getRow() {
+ return (T) cell.getRow();
+ }
+
+ /**
+ * Get the index of the row that was active when this Event was fired. NOTE:
+ * do <i>NOT</i> rely on this information remaining accurate after leaving
+ * the event handler.
+ *
+ * @return an integer value
+ */
+ public int getRowIndex() {
+ return cell.getRowIndex();
+ }
+
+ /**
+ * Get a reference to the column that was active when this Event was fired.
+ * NOTE: do <i>NOT</i> rely on this information remaining accurate after
+ * leaving the event handler.
+ *
+ * @return a column object
+ */
+ @SuppressWarnings("unchecked")
+ public <C, T> Column<C, T> getColumn() {
+ return (Column<C, T>) cell.getColumn();
+ }
+
+ /**
+ * Get the index of the column that was active when this Event was fired.
+ * NOTE: do <i>NOT</i> rely on this information remaining accurate after
+ * leaving the event handler.
+ *
+ * @return an integer value
+ */
+ public int getColumnIndex() {
+ return cell.getColumnIndex();
+ }
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java
new file mode 100644
index 0000000000..4f9396a9f1
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.widget.grid.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Common handler interface for editor events
+ */
+public interface EditorEventHandler extends EventHandler {
+
+ /**
+ * Action to perform when the editor has been opened
+ *
+ * @param e
+ * an editor open event object
+ */
+ public void onEditorOpen(EditorOpenEvent e);
+
+ /**
+ * Action to perform when the editor is re-opened on another row
+ *
+ * @param e
+ * an editor move event object
+ */
+ public void onEditorMove(EditorMoveEvent e);
+
+ /**
+ * Action to perform when the editor is closed
+ *
+ * @param e
+ * an editor close event object
+ */
+ public void onEditorClose(EditorCloseEvent e);
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java
new file mode 100644
index 0000000000..0e5e2dcd7b
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.widget.grid.events;
+
+import com.vaadin.client.widget.grid.CellReference;
+
+/**
+ * Event that gets fired when an already open editor is closed and re-opened on
+ * another row
+ */
+public class EditorMoveEvent extends EditorEvent {
+
+ public EditorMoveEvent(CellReference<?> cell) {
+ super(cell);
+ }
+
+ @Override
+ protected void dispatch(EditorEventHandler handler) {
+ handler.onEditorMove(this);
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java
new file mode 100644
index 0000000000..df0171945f
--- /dev/null
+++ b/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.widget.grid.events;
+
+import com.vaadin.client.widget.grid.CellReference;
+
+/**
+ * Event that gets fired when the editor is opened
+ */
+public class EditorOpenEvent extends EditorEvent {
+
+ public EditorOpenEvent(CellReference<?> cell) {
+ super(cell);
+ }
+
+ @Override
+ protected void dispatch(EditorEventHandler handler) {
+ handler.onEditorOpen(this);
+ }
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java b/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java
index 1e47d3ad6b..c64908f24c 100644
--- a/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java
+++ b/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java
@@ -56,6 +56,8 @@ import com.vaadin.client.widgets.Grid;
public class MultiSelectionRenderer<T> extends
ClickableRenderer<Boolean, CheckBox> {
+ private static final String SELECTION_CHECKBOX_CLASSNAME = "-selection-checkbox";
+
/** The size of the autoscroll area, both top and bottom. */
private static final int SCROLL_AREA_GRADIENT_PX = 100;
@@ -591,6 +593,8 @@ public class MultiSelectionRenderer<T> extends
@Override
public CheckBox createWidget() {
final CheckBox checkBox = GWT.create(CheckBox.class);
+ checkBox.setStylePrimaryName(grid.getStylePrimaryName()
+ + SELECTION_CHECKBOX_CLASSNAME);
CheckBoxEventHandler handler = new CheckBoxEventHandler(checkBox);
// Sink events
diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java
index 436b512294..43eeb7a0ce 100644
--- a/client/src/com/vaadin/client/widgets/Escalator.java
+++ b/client/src/com/vaadin/client/widgets/Escalator.java
@@ -29,11 +29,13 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import com.google.gwt.animation.client.Animation;
import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
@@ -48,6 +50,7 @@ import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.logging.client.LogConfiguration;
import com.google.gwt.user.client.Command;
@@ -72,6 +75,7 @@ import com.vaadin.client.widget.escalator.PositionFunction.AbsolutePosition;
import com.vaadin.client.widget.escalator.PositionFunction.Translate3DPosition;
import com.vaadin.client.widget.escalator.PositionFunction.TranslatePosition;
import com.vaadin.client.widget.escalator.PositionFunction.WebkitTranslate3DPosition;
+import com.vaadin.client.widget.escalator.Row;
import com.vaadin.client.widget.escalator.RowContainer;
import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer;
import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent;
@@ -302,8 +306,6 @@ public class Escalator extends Widget implements RequiresResize,
static class JsniUtil {
public static class TouchHandlerBundle {
- private static final double FLICK_POLL_FREQUENCY = 100d;
-
/**
* A <a href=
* "http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsOverlay.html"
@@ -338,105 +340,8 @@ public class Escalator extends Widget implements RequiresResize,
}-*/;
}
- private double touches = 0;
- private int lastX = 0;
- private int lastY = 0;
- private boolean snappedScrollEnabled = true;
- private double deltaX = 0;
- private double deltaY = 0;
-
private final Escalator escalator;
- private CustomTouchEvent latestTouchMoveEvent;
-
- /** The timestamp of {@link #flickPageX1} and {@link #flickPageY1} */
- private double flickStartTime = 0;
-
- /** The timestamp of {@link #flickPageX2} and {@link #flickPageY2} */
- private double flickTimestamp = 0;
-
- /** The most recent flick touch reference Y */
- private double flickPageY1 = -1;
- /** The most recent flick touch reference X */
- private double flickPageX1 = -1;
-
- /** The previous flick touch reference Y, before {@link #flickPageY1} */
- private double flickPageY2 = -1;
- /** The previous flick touch reference X, before {@link #flickPageX1} */
- private double flickPageX2 = -1;
-
- /**
- * This animation callback guarantees the fact that we don't scroll
- * the grid more than once per visible frame.
- *
- * It seems that there will never be more touch events than there
- * are rendered frames, but there's no guarantee for that. If it was
- * guaranteed, we probably could do all of this immediately in
- * {@link #touchMove(CustomTouchEvent)}, instead of deferring it
- * over here.
- */
- private AnimationCallback mover = new AnimationCallback() {
-
- @Override
- public void execute(double timestamp) {
- if (touches != 1) {
- return;
- }
-
- final int x = latestTouchMoveEvent.getPageX();
- final int y = latestTouchMoveEvent.getPageY();
-
- /*
- * Check if we need a new flick coordinate sample ( more
- * than FLICK_POLL_FREQUENCY ms have passed since the last
- * sample )
- */
- if (System.currentTimeMillis() - flickTimestamp > FLICK_POLL_FREQUENCY) {
-
- flickTimestamp = System.currentTimeMillis();
- // Set target coordinates
- flickPageY2 = y;
- flickPageX2 = x;
- }
-
- deltaX = x - lastX;
- deltaY = y - lastY;
- lastX = x;
- lastY = y;
-
- // snap the scroll to the major axes, at first.
- if (snappedScrollEnabled) {
- final double oldDeltaX = deltaX;
- final double oldDeltaY = deltaY;
-
- /*
- * Scrolling snaps to 40 degrees vs. flick scroll's 30
- * degrees, since slow movements have poor resolution -
- * it's easy to interpret a slight angle as a steep
- * angle, since the sample rate is "unnecessarily" high.
- * 40 simply felt better than 30.
- */
- final double[] snapped = Escalator.snapDeltas(deltaX,
- deltaY, RATIO_OF_40_DEGREES);
- deltaX = snapped[0];
- deltaY = snapped[1];
-
- /*
- * if the snap failed once, let's follow the pointer
- * from now on.
- */
- if (oldDeltaX != 0 && deltaX == oldDeltaX
- && oldDeltaY != 0 && deltaY == oldDeltaY) {
- snappedScrollEnabled = false;
- }
- }
-
- moveScrollFromEvent(escalator, -deltaX, -deltaY,
- latestTouchMoveEvent.getNativeEvent());
- }
- };
- private AnimationHandle animationHandle;
-
public TouchHandlerBundle(final Escalator escalator) {
this.escalator = escalator;
}
@@ -468,79 +373,157 @@ public class Escalator extends Widget implements RequiresResize,
});
}-*/;
- public void touchStart(final CustomTouchEvent event) {
- touches = event.getNativeEvent().getTouches().length();
- if (touches != 1) {
- return;
+ // Duration of the inertial scrolling simulation. Devices with
+ // larger screens take longer durations.
+ private static final int DURATION = (int)Window.getClientHeight();
+ // multiply scroll velocity with repeated touching
+ private int acceleration = 1;
+ private boolean touching = false;
+ // Two movement objects for storing status and processing touches
+ private Movement yMov, xMov;
+ final double MIN_VEL = 0.6, MAX_VEL = 4, F_VEL = 1500, F_ACC = 0.7, F_AXIS = 1;
+
+ // The object to deal with one direction scrolling
+ private class Movement {
+ final List<Double> speeds = new ArrayList<Double>();
+ final ScrollbarBundle scroll;
+ double position, offset, velocity, prevPos, prevTime, delta;
+ boolean run, vertical;
+
+ public Movement(boolean vertical) {
+ this.vertical = vertical;
+ scroll = vertical ? escalator.verticalScrollbar : escalator.horizontalScrollbar;
}
- escalator.scroller.cancelFlickScroll();
+ public void startTouch(CustomTouchEvent event) {
+ speeds.clear();
+ prevPos = pagePosition(event);
+ prevTime = Duration.currentTimeMillis();
+ }
+ public void moveTouch(CustomTouchEvent event) {
+ double pagePosition = pagePosition(event);
+ if (pagePosition > -1) {
+ delta = prevPos - pagePosition;
+ double now = Duration.currentTimeMillis();
+ double ellapsed = now - prevTime;
+ velocity = delta / ellapsed;
+ // if last speed was so low, reset speeds and start storing again
+ if (speeds.size() > 0 && !validSpeed(speeds.get(0))) {
+ speeds.clear();
+ run = true;
+ }
+ speeds.add(0, velocity);
+ prevTime = now;
+ prevPos = pagePosition;
+ }
+ }
+ public void endTouch(CustomTouchEvent event) {
+ // Compute average speed
+ velocity = 0;
+ for (double s : speeds) {
+ velocity += s / speeds.size();
+ }
+ position = scroll.getScrollPos();
+ // Compute offset, and adjust it with an easing curve so as movement is smoother.
+ offset = F_VEL * velocity * acceleration * easingInOutCos(velocity, MAX_VEL);
+ // Check that offset does not over-scroll
+ double minOff = -scroll.getScrollPos();
+ double maxOff = scroll.getScrollSize() - scroll.getOffsetSize() + minOff;
+ offset = Math.min(Math.max(offset, minOff), maxOff);
+ // Enable or disable inertia movement in this axis
+ run = validSpeed(velocity) && minOff < 0 && maxOff > 0;
+ if (run) {
+ event.getNativeEvent().preventDefault();
+ }
+ }
+ void validate(Movement other) {
+ if (!run || other.velocity > 0 && Math.abs(velocity / other.velocity) < F_AXIS) {
+ delta = offset = 0;
+ run = false;
+ }
+ }
+ void stepAnimation(double progress) {
+ scroll.setScrollPos(position + offset * progress);
+ }
- lastX = event.getPageX();
- lastY = event.getPageY();
+ int pagePosition(CustomTouchEvent event) {
+ JsArray<Touch> a = event.getNativeEvent().getTouches();
+ return vertical ? a.get(0).getPageY() : a.get(0).getPageX();
+ }
+ boolean validSpeed(double speed) {
+ return Math.abs(speed) > MIN_VEL;
+ }
+ }
- // Reset flick parameters
- flickPageX1 = lastX;
- flickPageX2 = -1;
- flickPageY1 = lastY;
- flickPageY2 = -1;
- flickStartTime = System.currentTimeMillis();
- flickTimestamp = 0;
+ // Using GWT animations which take care of native animation frames.
+ private Animation animation = new Animation() {
+ public void onUpdate(double progress) {
+ xMov.stepAnimation(progress);
+ yMov.stepAnimation(progress);
+ }
+ public double interpolate(double progress) {
+ return easingOutCirc(progress);
+ };
+ public void onComplete() {
+ touching = false;
+ escalator.body.domSorter.reschedule();
+ };
+ public void run(int duration) {
+ if (xMov.run || yMov.run) {
+ super.run(duration);
+ } else {
+ onComplete();
+ }
+ };
+ };
- snappedScrollEnabled = true;
+ public void touchStart(final CustomTouchEvent event) {
+ if (event.getNativeEvent().getTouches().length() == 1) {
+ if (yMov == null) {
+ yMov = new Movement(true);
+ xMov = new Movement(false);
+ }
+ if (animation.isRunning()) {
+ acceleration += F_ACC;
+ event.getNativeEvent().preventDefault();
+ animation.cancel();
+ } else {
+ acceleration = 1;
+ }
+ xMov.startTouch(event);
+ yMov.startTouch(event);
+ touching = true;
+ }
}
public void touchMove(final CustomTouchEvent event) {
- /*
- * since we only use the getPageX/Y, and calculate the diff
- * within the handler, we don't need to calculate any
- * intermediate deltas.
- */
- latestTouchMoveEvent = event;
-
- if (animationHandle != null) {
- animationHandle.cancel();
- }
- animationHandle = AnimationScheduler.get()
- .requestAnimationFrame(mover, escalator.bodyElem);
- event.getNativeEvent().preventDefault();
+ xMov.moveTouch(event);
+ yMov.moveTouch(event);
+ xMov.validate(yMov);
+ yMov.validate(xMov);
+ moveScrollFromEvent(escalator, xMov.delta, yMov.delta, event.getNativeEvent());
}
public void touchEnd(final CustomTouchEvent event) {
- touches = event.getNativeEvent().getTouches().length();
-
- if (touches == 0) {
-
- /*
- * We want to smooth the flick calculations here. We have
- * taken a frame of reference every FLICK_POLL_FREQUENCY.
- * But if the sample is too fresh, we might introduce noise
- * in our sampling, so we use the older sample instead. it
- * might be less accurate, but it's smoother.
- *
- * flickPage?1 is the most recent one, while flickPage?2 is
- * the previous one.
- */
-
- final double finalPageY;
- final double finalPageX;
- double deltaT = flickTimestamp - flickStartTime;
- boolean onlyOneSample = flickPageX2 < 0 || flickPageY2 < 0;
- if (onlyOneSample) {
- finalPageX = latestTouchMoveEvent.getPageX();
- finalPageY = latestTouchMoveEvent.getPageY();
- } else {
- finalPageY = flickPageY2;
- finalPageX = flickPageX2;
- }
-
- double deltaX = finalPageX - flickPageX1;
- double deltaY = finalPageY - flickPageY1;
+ xMov.endTouch(event);
+ yMov.endTouch(event);
+ xMov.validate(yMov);
+ yMov.validate(xMov);
+ // Adjust duration so as longer movements take more duration
+ boolean vert = !xMov.run || yMov.run && Math.abs(yMov.offset) > Math.abs(xMov.offset);
+ double delta = Math.abs((vert ? yMov : xMov).offset);
+ animation.run((int)(3 * DURATION * easingOutExp(delta)));
+ }
- escalator.scroller
- .handleFlickScroll(deltaX, deltaY, deltaT);
- escalator.body.domSorter.reschedule();
- }
+ private double easingInOutCos(double val, double max) {
+ return 0.5 - 0.5 * Math.cos(Math.PI * Math.signum(val)
+ * Math.min(Math.abs(val), max) / max);
+ }
+ private double easingOutExp(double delta) {
+ return (1 - Math.pow(2, -delta / 1000));
+ }
+ private double easingOutCirc(double progress) {
+ return Math.sqrt(1 - (progress - 1) * (progress - 1));
}
}
@@ -570,117 +553,6 @@ public class Escalator extends Widget implements RequiresResize,
}
}
- /**
- * The animation callback that handles the animation of a touch-scrolling
- * flick with inertia.
- */
- private class FlickScrollAnimator implements AnimationCallback {
- private static final double MIN_MAGNITUDE = 0.005;
- private static final double MAX_SPEED = 7;
-
- private double velX;
- private double velY;
- private double prevTime = 0;
- private int millisLeft;
- private double xFric;
- private double yFric;
-
- private boolean cancelled = false;
- private double lastLeft;
- private double lastTop;
-
- /**
- * Creates a new animation callback to handle touch-scrolling flick with
- * inertia.
- *
- * @param deltaX
- * the last scrolling delta in the x-axis in a touchmove
- * @param deltaY
- * the last scrolling delta in the y-axis in a touchmove
- * @param lastTime
- * the timestamp of the last touchmove
- */
- public FlickScrollAnimator(final double deltaX, final double deltaY,
- final double deltaT) {
- velX = Math.max(Math.min(deltaX / deltaT, MAX_SPEED), -MAX_SPEED);
- velY = Math.max(Math.min(deltaY / deltaT, MAX_SPEED), -MAX_SPEED);
-
- lastLeft = horizontalScrollbar.getScrollPos();
- lastTop = verticalScrollbar.getScrollPos();
-
- /*
- * If we're scrolling mainly in one of the four major directions,
- * and only a teeny bit to any other side, snap the scroll to that
- * major direction instead.
- */
- final double[] snapDeltas = Escalator.snapDeltas(velX, velY,
- RATIO_OF_30_DEGREES);
- velX = snapDeltas[0];
- velY = snapDeltas[1];
-
- if (velX * velX + velY * velY > MIN_MAGNITUDE) {
- millisLeft = 1500;
- xFric = velX / millisLeft;
- yFric = velY / millisLeft;
- } else {
- millisLeft = 0;
- }
-
- }
-
- @Override
- public void execute(final double doNotUseThisTimestamp) {
- /*
- * We cannot use the timestamp provided to this method since it is
- * of a format that cannot be determined at will. Therefore, we need
- * a timestamp format that we can handle, so our calculations are
- * correct.
- */
-
- if (millisLeft <= 0 || cancelled) {
- scroller.currentFlickScroller = null;
- return;
- }
-
- final double timestamp = Duration.currentTimeMillis();
- if (prevTime == 0) {
- prevTime = timestamp;
- AnimationScheduler.get().requestAnimationFrame(this);
- return;
- }
-
- double currentLeft = horizontalScrollbar.getScrollPos();
- double currentTop = verticalScrollbar.getScrollPos();
-
- final double timeDiff = timestamp - prevTime;
- double left = currentLeft - velX * timeDiff;
- setScrollLeft(left);
- velX -= xFric * timeDiff;
-
- double top = currentTop - velY * timeDiff;
- setScrollTop(top);
- velY -= yFric * timeDiff;
-
- cancelBecauseOfEdgeOrCornerMaybe();
-
- prevTime = timestamp;
- millisLeft -= timeDiff;
- lastLeft = currentLeft;
- lastTop = currentTop;
- AnimationScheduler.get().requestAnimationFrame(this);
- }
-
- private void cancelBecauseOfEdgeOrCornerMaybe() {
- if (lastLeft == horizontalScrollbar.getScrollPos()
- && lastTop == verticalScrollbar.getScrollPos()) {
- cancel();
- }
- }
-
- public void cancel() {
- cancelled = true;
- }
- }
/**
* ScrollDestination case-specific handling logic.
@@ -760,11 +632,6 @@ public class Escalator extends Widget implements RequiresResize,
private class Scroller extends JsniWorkaround {
private double lastScrollTop = 0;
private double lastScrollLeft = 0;
- /**
- * The current flick scroll animator. This is <code>null</code> if the
- * view isn't animating a flick scroll at the moment.
- */
- private FlickScrollAnimator currentFlickScroller;
public Scroller() {
super(Escalator.this);
@@ -782,7 +649,7 @@ public class Escalator extends Widget implements RequiresResize,
return $entry(function(e) {
var target = e.target || e.srcElement; // IE8 uses e.scrElement
-
+
// in case the scroll event was native (i.e. scrollbars were dragged, or
// the scrollTop/Left was manually modified), the bundles have old cache
// values. We need to make sure that the caches are kept up to date.
@@ -1100,30 +967,6 @@ public class Escalator extends Widget implements RequiresResize,
}
}-*/;
- private void cancelFlickScroll() {
- if (currentFlickScroller != null) {
- currentFlickScroller.cancel();
- }
- }
-
- /**
- * Handles a touch-based flick scroll.
- *
- * @param deltaX
- * the last scrolling delta in the x-axis in a touchmove
- * @param deltaY
- * the last scrolling delta in the y-axis in a touchmove
- * @param lastTime
- * the timestamp of the last touchmove
- */
- public void handleFlickScroll(double deltaX, double deltaY,
- double deltaT) {
- currentFlickScroller = new FlickScrollAnimator(deltaX, deltaY,
- deltaT);
- AnimationScheduler.get()
- .requestAnimationFrame(currentFlickScroller);
- }
-
public void scrollToColumn(final int columnIndex,
final ScrollDestination destination, final int padding) {
assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column";
@@ -1475,6 +1318,9 @@ public class Escalator extends Widget implements RequiresResize,
cellElem.addClassName("frozen");
position.set(cellElem, scroller.lastScrollLeft, 0);
}
+ if (columnConfiguration.frozenColumns > 0 && col == columnConfiguration.frozenColumns - 1) {
+ cellElem.addClassName("last-frozen");
+ }
}
referenceRow = paintInsertRow(referenceRow, tr, row);
@@ -1732,6 +1578,14 @@ public class Escalator extends Widget implements RequiresResize,
}
public void setColumnFrozen(int column, boolean frozen) {
+ toggleFrozenColumnClass(column, frozen, "frozen");
+
+ if (frozen) {
+ updateFreezePosition(column, scroller.lastScrollLeft);
+ }
+ }
+
+ private void toggleFrozenColumnClass(int column, boolean frozen, String className) {
final NodeList<TableRowElement> childRows = root.getRows();
for (int row = 0; row < childRows.getLength(); row++) {
@@ -1742,16 +1596,16 @@ public class Escalator extends Widget implements RequiresResize,
TableCellElement cell = tr.getCells().getItem(column);
if (frozen) {
- cell.addClassName("frozen");
+ cell.addClassName(className);
} else {
- cell.removeClassName("frozen");
+ cell.removeClassName(className);
position.reset(cell);
}
}
+ }
- if (frozen) {
- updateFreezePosition(column, scroller.lastScrollLeft);
- }
+ public void setColumnLastFrozen(int column, boolean lastFrozen) {
+ toggleFrozenColumnClass(column, lastFrozen, "last-frozen");
}
public void updateFreezePosition(int column, double scrollLeft) {
@@ -2470,9 +2324,9 @@ public class Escalator extends Widget implements RequiresResize,
private boolean sortIfConditionsMet() {
boolean enoughFramesHavePassed = framesPassed >= REQUIRED_FRAMES_PASSED;
boolean enoughTimeHasPassed = (Duration.currentTimeMillis() - startTime) >= SORT_DELAY_MILLIS;
- boolean notAnimatingFlick = (scroller.currentFlickScroller == null);
+ boolean notTouchActivity = !scroller.touchHandlerBundle.touching;
boolean conditionsMet = enoughFramesHavePassed
- && enoughTimeHasPassed && notAnimatingFlick;
+ && enoughTimeHasPassed && notTouchActivity;
if (conditionsMet) {
resetConditions();
@@ -2652,15 +2506,7 @@ public class Escalator extends Widget implements RequiresResize,
if (rowsWereMoved) {
fireRowVisibilityChangeEvent();
-
- if (scroller.touchHandlerBundle.touches == 0) {
- /*
- * this will never be called on touch scrolling. That is
- * handled separately and explicitly by
- * TouchHandlerBundle.touchEnd();
- */
- domSorter.reschedule();
- }
+ domSorter.reschedule();
}
}
@@ -2756,17 +2602,33 @@ public class Escalator extends Widget implements RequiresResize,
// move the surrounding rows to their correct places.
double rowTop = (unupdatedLogicalStart + (end - start))
* getDefaultRowHeight();
- final ListIterator<TableRowElement> i = visualRowOrder
- .listIterator(visualTargetIndex + (end - start));
-
- int logicalRowIndexCursor = unupdatedLogicalStart;
- while (i.hasNext()) {
- rowTop += spacerContainer
- .getSpacerHeight(logicalRowIndexCursor++);
- final TableRowElement tr = i.next();
- setRowPosition(tr, 0, rowTop);
- rowTop += getDefaultRowHeight();
+ // TODO: Get rid of this try/catch block by fixing the
+ // underlying issue. The reason for this erroneous behavior
+ // might be that Escalator actually works 'by mistake', and
+ // the order of operations is, in fact, wrong.
+ try {
+ final ListIterator<TableRowElement> i = visualRowOrder
+ .listIterator(visualTargetIndex + (end - start));
+
+ int logicalRowIndexCursor = unupdatedLogicalStart;
+ while (i.hasNext()) {
+ rowTop += spacerContainer
+ .getSpacerHeight(logicalRowIndexCursor++);
+
+ final TableRowElement tr = i.next();
+ setRowPosition(tr, 0, rowTop);
+ rowTop += getDefaultRowHeight();
+ }
+ } catch (Exception e) {
+ Logger logger = getLogger();
+ logger.warning("Ignored out-of-bounds row element access");
+ logger.warning("Escalator state: start=" + start
+ + ", end=" + end + ", visualTargetIndex="
+ + visualTargetIndex
+ + ", visualRowOrder.size()="
+ + visualRowOrder.size());
+ logger.warning(e.toString());
}
}
@@ -4309,6 +4171,17 @@ public class Escalator extends Widget implements RequiresResize,
firstUnaffectedCol = oldCount;
}
+ if (oldCount > 0) {
+ header.setColumnLastFrozen(oldCount - 1, false);
+ body.setColumnLastFrozen(oldCount - 1, false);
+ footer.setColumnLastFrozen(oldCount - 1, false);
+ }
+ if (count > 0) {
+ header.setColumnLastFrozen(count - 1, true);
+ body.setColumnLastFrozen(count - 1, true);
+ footer.setColumnLastFrozen(count - 1, true);
+ }
+
for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) {
header.setColumnFrozen(col, frozen);
body.setColumnFrozen(col, frozen);
@@ -5619,14 +5492,29 @@ public class Escalator extends Widget implements RequiresResize,
horizontalScrollbar.setScrollbarThickness(scrollbarThickness);
horizontalScrollbar
.addVisibilityHandler(new ScrollbarBundle.VisibilityHandler() {
+
+ private boolean queued = false;
+
@Override
public void visibilityChanged(
ScrollbarBundle.VisibilityChangeEvent event) {
+ if (queued) {
+ return;
+ }
+ queued = true;
+
/*
* We either lost or gained a scrollbar. In any case, we
* need to change the height, if it's defined by rows.
*/
- applyHeightByRows();
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ applyHeightByRows();
+ queued = false;
+ }
+ });
}
});
@@ -6022,10 +5910,14 @@ public class Escalator extends Widget implements RequiresResize,
public void scrollToRow(final int rowIndex,
final ScrollDestination destination, final int padding)
throws IndexOutOfBoundsException, IllegalArgumentException {
- validateScrollDestination(destination, padding);
- verifyValidRowIndex(rowIndex);
-
- scroller.scrollToRow(rowIndex, destination, padding);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ validateScrollDestination(destination, padding);
+ verifyValidRowIndex(rowIndex);
+ scroller.scrollToRow(rowIndex, destination, padding);
+ }
+ });
}
private void verifyValidRowIndex(final int rowIndex) {
@@ -6086,55 +5978,62 @@ public class Escalator extends Widget implements RequiresResize,
* {@code destination == null}; or if {@code rowIndex == -1} and
* there is no spacer open at that index.
*/
- public void scrollToRowAndSpacer(int rowIndex,
- ScrollDestination destination, int padding)
+ public void scrollToRowAndSpacer(final int rowIndex,
+ final ScrollDestination destination, final int padding)
throws IllegalArgumentException {
- validateScrollDestination(destination, padding);
- if (rowIndex != -1) {
- verifyValidRowIndex(rowIndex);
- }
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ validateScrollDestination(destination, padding);
+ if (rowIndex != -1) {
+ verifyValidRowIndex(rowIndex);
+ }
- // row range
- final Range rowRange;
- if (rowIndex != -1) {
- int rowTop = (int) Math.floor(body.getRowTop(rowIndex));
- int rowHeight = (int) Math.ceil(body.getDefaultRowHeight());
- rowRange = Range.withLength(rowTop, rowHeight);
- } else {
- rowRange = Range.withLength(0, 0);
- }
+ // row range
+ final Range rowRange;
+ if (rowIndex != -1) {
+ int rowTop = (int) Math.floor(body.getRowTop(rowIndex));
+ int rowHeight = (int) Math.ceil(body.getDefaultRowHeight());
+ rowRange = Range.withLength(rowTop, rowHeight);
+ } else {
+ rowRange = Range.withLength(0, 0);
+ }
- // get spacer
- final SpacerContainer.SpacerImpl spacer = body.spacerContainer
- .getSpacer(rowIndex);
+ // get spacer
+ final SpacerContainer.SpacerImpl spacer = body.spacerContainer
+ .getSpacer(rowIndex);
- if (rowIndex == -1 && spacer == null) {
- throw new IllegalArgumentException("Cannot scroll to row index "
- + "-1, as there is no spacer open at that index.");
- }
+ if (rowIndex == -1 && spacer == null) {
+ throw new IllegalArgumentException(
+ "Cannot scroll to row index "
+ + "-1, as there is no spacer open at that index.");
+ }
- // make into target range
- final Range targetRange;
- if (spacer != null) {
- final int spacerTop = (int) Math.floor(spacer.getTop());
- final int spacerHeight = (int) Math.ceil(spacer.getHeight());
- Range spacerRange = Range.withLength(spacerTop, spacerHeight);
+ // make into target range
+ final Range targetRange;
+ if (spacer != null) {
+ final int spacerTop = (int) Math.floor(spacer.getTop());
+ final int spacerHeight = (int) Math.ceil(spacer.getHeight());
+ Range spacerRange = Range.withLength(spacerTop,
+ spacerHeight);
- targetRange = rowRange.combineWith(spacerRange);
- } else {
- targetRange = rowRange;
- }
+ targetRange = rowRange.combineWith(spacerRange);
+ } else {
+ targetRange = rowRange;
+ }
- // get params
- int targetStart = targetRange.getStart();
- int targetEnd = targetRange.getEnd();
- double viewportStart = getScrollTop();
- double viewportEnd = viewportStart + body.getHeightOfSection();
+ // get params
+ int targetStart = targetRange.getStart();
+ int targetEnd = targetRange.getEnd();
+ double viewportStart = getScrollTop();
+ double viewportEnd = viewportStart + body.getHeightOfSection();
- double scrollPos = getScrollPos(destination, targetStart, targetEnd,
- viewportStart, viewportEnd, padding);
+ double scrollPos = getScrollPos(destination, targetStart,
+ targetEnd, viewportStart, viewportEnd, padding);
- setScrollTop(scrollPos);
+ setScrollTop(scrollPos);
+ }
+ });
}
private static void validateScrollDestination(
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index 58fc532a77..fc8151272d 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -31,7 +31,6 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
-import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
@@ -42,6 +41,7 @@ import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
@@ -49,6 +49,9 @@ import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
@@ -79,6 +82,7 @@ import com.vaadin.client.Focusable;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.data.DataChangeHandler;
import com.vaadin.client.data.DataSource;
+import com.vaadin.client.data.DataSource.RowHandle;
import com.vaadin.client.renderers.ComplexRenderer;
import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.renderers.WidgetRenderer;
@@ -103,6 +107,7 @@ import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
import com.vaadin.client.widget.grid.DataAvailableEvent;
import com.vaadin.client.widget.grid.DataAvailableHandler;
+import com.vaadin.client.widget.grid.DefaultEditorEventHandler;
import com.vaadin.client.widget.grid.DetailsGenerator;
import com.vaadin.client.widget.grid.EditorHandler;
import com.vaadin.client.widget.grid.EditorHandler.EditorRequest;
@@ -121,6 +126,9 @@ import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
+import com.vaadin.client.widget.grid.events.EditorCloseEvent;
+import com.vaadin.client.widget.grid.events.EditorEvent;
+import com.vaadin.client.widget.grid.events.EditorEventHandler;
import com.vaadin.client.widget.grid.events.FooterClickHandler;
import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler;
import com.vaadin.client.widget.grid.events.FooterKeyDownHandler;
@@ -206,8 +214,10 @@ import com.vaadin.shared.util.SharedUtil;
* @author Vaadin Ltd
*/
public class Grid<T> extends ResizeComposite implements
- HasSelectionHandlers<T>, SubPartAware, DeferredWorker, HasWidgets,
- HasEnabled {
+ HasSelectionHandlers<T>, SubPartAware, DeferredWorker, Focusable,
+ com.google.gwt.user.client.ui.Focusable, HasWidgets, HasEnabled {
+
+ private static final String SELECT_ALL_CHECKBOX_CLASSNAME = "-select-all-checkbox";
/**
* Enum describing different sections of Grid.
@@ -1081,14 +1091,10 @@ public class Grid<T> extends ResizeComposite implements
}
completed = true;
- grid.getEditor().setErrorMessage(errorMessage);
-
- grid.getEditor().clearEditorColumnErrors();
- if (errorColumns != null) {
- for (Column<?, T> column : errorColumns) {
- grid.getEditor().setEditorColumnError(column, true);
- }
+ if (errorColumns == null) {
+ errorColumns = Collections.emptySet();
}
+ grid.getEditor().setEditorError(errorMessage, errorColumns);
}
@Override
@@ -1115,10 +1121,104 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * A wrapper for native DOM events originating from Grid. In addition to the
+ * native event, contains a {@link CellReference} instance specifying which
+ * cell the event originated from.
+ *
+ * @since
+ * @param <T>
+ * The row type of the grid
+ */
+ public static class GridEvent<T> {
+ private Event event;
+ private EventCellReference<T> cell;
+
+ protected GridEvent(Event event, EventCellReference<T> cell) {
+ this.event = event;
+ this.cell = cell;
+ }
+
+ /**
+ * Returns the wrapped DOM event.
+ *
+ * @return the DOM event
+ */
+ public Event getDomEvent() {
+ return event;
+ }
+
+ /**
+ * Returns the Grid cell this event originated from.
+ *
+ * @return the event cell
+ */
+ public EventCellReference<T> getCell() {
+ return cell;
+ }
+
+ /**
+ * Returns the Grid instance this event originated from.
+ *
+ * @return the grid
+ */
+ public Grid<T> getGrid() {
+ return cell.getGrid();
+ }
+ }
+
+ /**
+ * A wrapper for native DOM events related to the {@link Editor Grid editor}
+ * .
+ *
+ * @since
+ * @param <T>
+ * the row type of the grid
+ */
+ public static class EditorDomEvent<T> extends GridEvent<T> {
+
+ protected EditorDomEvent(Event event, EventCellReference<T> cell) {
+ super(event, cell);
+ }
+
+ /**
+ * Returns the editor of the Grid this event originated from.
+ *
+ * @return the related editor instance
+ */
+ public Editor<T> getEditor() {
+ return getGrid().getEditor();
+ }
+
+ /**
+ * Returns the row index the editor is open at. If the editor is not
+ * open, returns -1.
+ *
+ * @return the index of the edited row or -1 if editor is not open
+ */
+ public int getRowIndex() {
+ return getEditor().rowIndex;
+ }
+
+ /**
+ * Returns the column index the editor was opened at. If the editor is
+ * not open, returns -1.
+ *
+ * @return the column index or -1 if editor is not open
+ */
+ public int getFocusedColumnIndex() {
+ return getEditor().focusedColumnIndex;
+ }
+ }
+
+ /**
* An editor UI for Grid rows. A single Grid row at a time can be opened for
* editing.
+ *
+ * @since
+ * @param <T>
+ * the row type of the grid
*/
- protected static class Editor<T> {
+ public static class Editor<T> {
public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;
@@ -1126,15 +1226,41 @@ public class Grid<T> extends ResizeComposite implements
private static final String ERROR_CLASS_NAME = "error";
private static final String NOT_EDITABLE_CLASS_NAME = "not-editable";
+ /**
+ * A handler for events related to the Grid editor. Responsible for
+ * opening, moving or closing the editor based on the received event.
+ *
+ * @since
+ * @author Vaadin Ltd
+ * @param <T>
+ * the row type of the grid
+ */
+ public interface EventHandler<T> {
+ /**
+ * Handles editor-related events in an appropriate way. Opens,
+ * moves, or closes the editor based on the given event.
+ *
+ * @param event
+ * the received event
+ * @return true if the event was handled and nothing else should be
+ * done, false otherwise
+ */
+ boolean handleEvent(EditorDomEvent<T> event);
+ }
+
protected enum State {
INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING
}
private Grid<T> grid;
private EditorHandler<T> handler;
+ private EventHandler<T> eventHandler = GWT
+ .create(DefaultEditorEventHandler.class);
private DivElement editorOverlay = DivElement.as(DOM.createDiv());
private DivElement cellWrapper = DivElement.as(DOM.createDiv());
+ private DivElement frozenCellWrapper = DivElement.as(DOM.createDiv());
+
private DivElement messageAndButtonsWrapper = DivElement.as(DOM
.createDiv());
@@ -1146,15 +1272,25 @@ public class Grid<T> extends ResizeComposite implements
private DivElement message = DivElement.as(DOM.createDiv());
private Map<Column<?, T>, Widget> columnToWidget = new HashMap<Column<?, T>, Widget>();
+ private List<HandlerRegistration> focusHandlers = new ArrayList<HandlerRegistration>();
private boolean enabled = false;
private State state = State.INACTIVE;
private int rowIndex = -1;
- private int columnIndex = -1;
+ private int focusedColumnIndex = -1;
private String styleName = null;
+ /*
+ * Used to track Grid horizontal scrolling
+ */
private HandlerRegistration scrollHandler;
+ /*
+ * Used to open editor once Grid has vertically scrolled to the proper
+ * position and data is available
+ */
+ private HandlerRegistration dataAvailableHandler;
+
private final Button saveButton;
private final Button cancelButton;
@@ -1209,6 +1345,7 @@ public class Grid<T> extends ResizeComposite implements
+ " remember to call success() or fail()?");
}
};
+
private final EditorRequestImpl.RequestCallback<T> bindRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
@Override
public void onSuccess(EditorRequest<T> request) {
@@ -1216,10 +1353,7 @@ public class Grid<T> extends ResizeComposite implements
state = State.ACTIVE;
bindTimeout.cancel();
- assert rowIndex == request.getRowIndex() : "Request row index "
- + request.getRowIndex()
- + " did not match the saved row index " + rowIndex;
-
+ rowIndex = request.getRowIndex();
showOverlay();
}
}
@@ -1227,22 +1361,30 @@ public class Grid<T> extends ResizeComposite implements
@Override
public void onError(EditorRequest<T> request) {
if (state == State.BINDING) {
- state = State.INACTIVE;
+ if (rowIndex == -1) {
+ doCancel();
+ } else {
+ state = State.ACTIVE;
+ }
bindTimeout.cancel();
// TODO show something in the DOM as well?
getLogger().warning(
"An error occurred while trying to show the "
+ "Grid editor");
- grid.getEscalator().setScrollLocked(Direction.VERTICAL,
- false);
- updateSelectionCheckboxesAsNeeded(true);
}
}
};
/** A set of all the columns that display an error flag. */
private final Set<Column<?, T>> columnErrors = new HashSet<Grid.Column<?, T>>();
+ private boolean buffered = true;
+
+ /** Original position of editor */
+ private double originalTop;
+ /** Original scroll position of grid when editor was opened */
+ private double originalScrollTop;
+ private RowHandle<T> pinnedRowHandle;
public Editor() {
saveButton = new Button();
@@ -1264,7 +1406,9 @@ public class Grid<T> extends ResizeComposite implements
});
}
- public void setErrorMessage(String errorMessage) {
+ public void setEditorError(String errorMessage,
+ Collection<Column<?, T>> errorColumns) {
+
if (errorMessage == null) {
message.removeFromParent();
} else {
@@ -1273,6 +1417,17 @@ public class Grid<T> extends ResizeComposite implements
messageWrapper.appendChild(message);
}
}
+ // In unbuffered mode only show message wrapper if there is an error
+ if (!isBuffered()) {
+ setMessageAndButtonsWrapperVisible(errorMessage != null);
+ }
+
+ if (state == State.ACTIVE || state == State.SAVING) {
+ for (Column<?, T> c : grid.getColumns()) {
+ grid.getEditor().setEditorColumnError(c,
+ errorColumns.contains(c));
+ }
+ }
}
public int getRow() {
@@ -1280,12 +1435,26 @@ public class Grid<T> extends ResizeComposite implements
}
/**
- * Equivalent to {@code editRow(rowIndex, -1)}.
+ * If a cell of this Grid had focus once this editRow call was
+ * triggered, the editor component at the previously focused column
+ * index will be focused.
+ *
+ * If a Grid cell was not focused prior to calling this method, it will
+ * be equivalent to {@code editRow(rowIndex, -1)}.
*
* @see #editRow(int, int)
*/
public void editRow(int rowIndex) {
- editRow(rowIndex, -1);
+ // Focus the last focused column in the editor iff grid or its child
+ // was focused before the edit request
+ Cell focusedCell = grid.cellFocusHandler.getFocusedCell();
+ Element focusedElement = WidgetUtil.getFocusedElement();
+ if (focusedCell != null && focusedElement != null
+ && grid.getElement().isOrHasChild(focusedElement)) {
+ editRow(rowIndex, focusedCell.getColumn());
+ } else {
+ editRow(rowIndex, -1);
+ }
}
/**
@@ -1302,28 +1471,41 @@ public class Grid<T> extends ResizeComposite implements
* @throws IllegalStateException
* if this editor is not enabled
* @throws IllegalStateException
- * if this editor is already in edit mode
+ * if this editor is already in edit mode and in buffered
+ * mode
*
* @since 7.5
*/
- public void editRow(int rowIndex, int columnIndex) {
+ public void editRow(final int rowIndex, int columnIndex) {
if (!enabled) {
throw new IllegalStateException(
"Cannot edit row: editor is not enabled");
}
if (state != State.INACTIVE) {
- throw new IllegalStateException(
- "Cannot edit row: editor already in edit mode");
+ if (isBuffered()) {
+ throw new IllegalStateException(
+ "Cannot edit row: editor already in edit mode");
+ }
}
this.rowIndex = rowIndex;
- this.columnIndex = columnIndex;
-
+ this.focusedColumnIndex = columnIndex;
state = State.ACTIVATING;
if (grid.getEscalator().getVisibleRowRange().contains(rowIndex)) {
- show();
+ show(rowIndex);
} else {
+ hideOverlay();
+ dataAvailableHandler = grid
+ .addDataAvailableHandler(new DataAvailableHandler() {
+ @Override
+ public void onDataAvailable(DataAvailableEvent event) {
+ if (event.getAvailableRows().contains(rowIndex)) {
+ show(rowIndex);
+ dataAvailableHandler.removeHandler();
+ }
+ }
+ });
grid.scrollToRow(rowIndex, ScrollDestination.MIDDLE);
}
}
@@ -1346,14 +1528,18 @@ public class Grid<T> extends ResizeComposite implements
throw new IllegalStateException(
"Cannot cancel edit: editor is not in edit mode");
}
- hideOverlay();
- grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
+ handler.cancel(new EditorRequestImpl<T>(grid, rowIndex, null));
+ doCancel();
+ }
- EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex,
- null);
- handler.cancel(request);
+ private void doCancel() {
+ hideOverlay();
state = State.INACTIVE;
+ rowIndex = -1;
+ focusedColumnIndex = -1;
+ grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
updateSelectionCheckboxesAsNeeded(true);
+ grid.fireEvent(new EditorCloseEvent(grid.eventCell));
}
private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) {
@@ -1446,14 +1632,15 @@ public class Grid<T> extends ResizeComposite implements
this.enabled = enabled;
}
- protected void show() {
+ protected void show(int rowIndex) {
if (state == State.ACTIVATING) {
state = State.BINDING;
bindTimeout.schedule(BIND_TIMEOUT_MS);
EditorRequest<T> request = new EditorRequestImpl<T>(grid,
rowIndex, bindRequestCallback);
handler.bind(request);
- grid.getEscalator().setScrollLocked(Direction.VERTICAL, true);
+ grid.getEscalator().setScrollLocked(Direction.VERTICAL,
+ isBuffered());
updateSelectionCheckboxesAsNeeded(false);
}
}
@@ -1463,15 +1650,6 @@ public class Grid<T> extends ResizeComposite implements
assert this.grid == null : "Can only attach editor to Grid once";
this.grid = grid;
-
- grid.addDataAvailableHandler(new DataAvailableHandler() {
- @Override
- public void onDataAvailable(DataAvailableEvent event) {
- if (event.getAvailableRows().contains(rowIndex)) {
- show();
- }
- }
- });
}
protected State getState() {
@@ -1516,6 +1694,8 @@ public class Grid<T> extends ResizeComposite implements
* @since 7.5
*/
protected void showOverlay() {
+ // Ensure overlay is hidden initially
+ hideOverlay();
DivElement gridElement = DivElement.as(grid.getElement());
@@ -1526,19 +1706,38 @@ public class Grid<T> extends ResizeComposite implements
@Override
public void onScroll(ScrollEvent event) {
updateHorizontalScrollPosition();
+ if (!isBuffered()) {
+ updateVerticalScrollPosition();
+ }
}
});
gridElement.appendChild(editorOverlay);
+ editorOverlay.appendChild(frozenCellWrapper);
editorOverlay.appendChild(cellWrapper);
editorOverlay.appendChild(messageAndButtonsWrapper);
+ int frozenColumns = grid.getVisibleFrozenColumnCount();
+ double frozenColumnsWidth = 0;
+ double cellHeight = 0;
+
for (int i = 0; i < tr.getCells().getLength(); i++) {
Element cell = createCell(tr.getCells().getItem(i));
-
- cellWrapper.appendChild(cell);
+ cellHeight = Math.max(cellHeight, WidgetUtil
+ .getRequiredHeightBoundingClientRectDouble(tr
+ .getCells().getItem(i)));
Column<?, T> column = grid.getVisibleColumn(i);
+
+ if (i < frozenColumns) {
+ frozenCellWrapper.appendChild(cell);
+ frozenColumnsWidth += WidgetUtil
+ .getRequiredWidthBoundingClientRectDouble(tr
+ .getCells().getItem(i));
+ } else {
+ cellWrapper.appendChild(cell);
+ }
+
if (column.isEditable()) {
Widget editor = getHandler().getWidget(column);
@@ -1547,7 +1746,28 @@ public class Grid<T> extends ResizeComposite implements
attachWidget(editor, cell);
}
- if (i == columnIndex) {
+ final int currentColumnIndex = i;
+ if (editor instanceof HasFocusHandlers) {
+ // Use a proper focus handler if available
+ focusHandlers.add(((HasFocusHandlers) editor)
+ .addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ focusedColumnIndex = currentColumnIndex;
+ }
+ }));
+ } else {
+ // Try sniffing for DOM focus events
+ focusHandlers.add(editor.addDomHandler(
+ new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ focusedColumnIndex = currentColumnIndex;
+ }
+ }, FocusEvent.getType()));
+ }
+
+ if (i == focusedColumnIndex) {
if (editor instanceof Focusable) {
((Focusable) editor).focus();
} else if (editor instanceof com.google.gwt.user.client.ui.Focusable) {
@@ -1557,17 +1777,70 @@ public class Grid<T> extends ResizeComposite implements
}
} else {
cell.addClassName(NOT_EDITABLE_CLASS_NAME);
+ cell.addClassName(tr.getCells().getItem(i).getClassName());
+ // If the focused or frozen stylename is present it should
+ // not be inherited by the editor cell as it is not useful
+ // in the editor and would look broken without additional
+ // style rules. This is a bit of a hack.
+ cell.removeClassName(grid.cellFocusStyleName);
+ cell.removeClassName("frozen");
+
+ if (column == grid.selectionColumn) {
+ // Duplicate selection column CheckBox
+
+ pinnedRowHandle = grid.getDataSource().getHandle(
+ grid.getDataSource().getRow(rowIndex));
+ pinnedRowHandle.pin();
+
+ // We need to duplicate the selection CheckBox for the
+ // editor overlay since the original one is hidden by
+ // the overlay
+ final CheckBox checkBox = GWT.create(CheckBox.class);
+ checkBox.setValue(grid.isSelected(pinnedRowHandle
+ .getRow()));
+ checkBox.sinkEvents(Event.ONCLICK);
+
+ checkBox.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ T row = pinnedRowHandle.getRow();
+ if (grid.isSelected(row)) {
+ grid.deselect(row);
+ } else {
+ grid.select(row);
+ }
+ }
+ });
+ attachWidget(checkBox, cell);
+ columnToWidget.put(column, checkBox);
+
+ // Only enable CheckBox in non-buffered mode
+ checkBox.setEnabled(!isBuffered());
+
+ } else if (!(column.getRenderer() instanceof WidgetRenderer)) {
+ // Copy non-widget content directly
+ cell.setInnerHTML(tr.getCells().getItem(i)
+ .getInnerHTML());
+ }
}
}
+ setBounds(frozenCellWrapper, 0, 0, frozenColumnsWidth, 0);
+ setBounds(cellWrapper, frozenColumnsWidth, 0, tr.getOffsetWidth()
+ - frozenColumnsWidth, cellHeight);
+
// Only add these elements once
if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) {
messageAndButtonsWrapper.appendChild(messageWrapper);
messageAndButtonsWrapper.appendChild(buttonsWrapper);
}
- attachWidget(saveButton, buttonsWrapper);
- attachWidget(cancelButton, buttonsWrapper);
+ if (isBuffered()) {
+ attachWidget(saveButton, buttonsWrapper);
+ attachWidget(cancelButton, buttonsWrapper);
+ }
+
+ setMessageAndButtonsWrapperVisible(isBuffered());
updateHorizontalScrollPosition();
@@ -1579,9 +1852,11 @@ public class Grid<T> extends ResizeComposite implements
int gridTop = gridElement.getAbsoluteTop();
double overlayTop = rowTop + bodyTop - gridTop;
- if (buttonsShouldBeRenderedBelow(tr)) {
+ originalScrollTop = grid.getScrollTop();
+ if (!isBuffered() || buttonsShouldBeRenderedBelow(tr)) {
// Default case, editor buttons are below the edited row
editorOverlay.getStyle().setTop(overlayTop, Unit.PX);
+ originalTop = overlayTop;
editorOverlay.getStyle().clearBottom();
} else {
// Move message and buttons wrapper on top of cell wrapper if
@@ -1614,6 +1889,20 @@ public class Grid<T> extends ResizeComposite implements
}
protected void hideOverlay() {
+ if (editorOverlay.getParentElement() == null) {
+ return;
+ }
+
+ if (pinnedRowHandle != null) {
+ pinnedRowHandle.unpin();
+ pinnedRowHandle = null;
+ }
+
+ for (HandlerRegistration r : focusHandlers) {
+ r.removeHandler();
+ }
+ focusHandlers.clear();
+
for (Widget w : columnToWidget.values()) {
setParent(w, null);
}
@@ -1624,11 +1913,16 @@ public class Grid<T> extends ResizeComposite implements
editorOverlay.removeAllChildren();
cellWrapper.removeAllChildren();
+ frozenCellWrapper.removeAllChildren();
editorOverlay.removeFromParent();
scrollHandler.removeHandler();
clearEditorColumnErrors();
+
+ if (focusedColumnIndex != -1) {
+ grid.focusCell(rowIndex, focusedColumnIndex);
+ }
}
protected void setStylePrimaryName(String primaryName) {
@@ -1636,6 +1930,7 @@ public class Grid<T> extends ResizeComposite implements
editorOverlay.removeClassName(styleName);
cellWrapper.removeClassName(styleName + "-cells");
+ frozenCellWrapper.removeClassName(styleName + "-cells");
messageAndButtonsWrapper.removeClassName(styleName + "-footer");
messageWrapper.removeClassName(styleName + "-message");
@@ -1648,6 +1943,7 @@ public class Grid<T> extends ResizeComposite implements
editorOverlay.setClassName(styleName);
cellWrapper.setClassName(styleName + "-cells");
+ frozenCellWrapper.setClassName(styleName + "-cells frozen");
messageAndButtonsWrapper.setClassName(styleName + "-footer");
messageWrapper.setClassName(styleName + "-message");
@@ -1698,7 +1994,37 @@ public class Grid<T> extends ResizeComposite implements
private void updateHorizontalScrollPosition() {
double scrollLeft = grid.getScrollLeft();
- cellWrapper.getStyle().setLeft(-scrollLeft, Unit.PX);
+ cellWrapper.getStyle().setLeft(
+ frozenCellWrapper.getOffsetWidth() - scrollLeft, Unit.PX);
+ }
+
+ /**
+ * Moves the editor overlay on scroll so that it stays on top of the
+ * edited row. This will also snap the editor to top or bottom of the
+ * row container if the edited row is scrolled out of the visible area.
+ */
+ private void updateVerticalScrollPosition() {
+ double newScrollTop = grid.getScrollTop();
+
+ int gridTop = grid.getElement().getAbsoluteTop();
+ int editorHeight = editorOverlay.getOffsetHeight();
+
+ Escalator escalator = grid.getEscalator();
+ TableSectionElement header = escalator.getHeader().getElement();
+ int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
+ int headerBottom = header.getAbsoluteBottom();
+
+ double newTop = originalTop - (newScrollTop - originalScrollTop);
+
+ if (newTop + gridTop < headerBottom) {
+ // Snap editor to top of the row container
+ newTop = header.getOffsetHeight();
+ } else if (newTop + gridTop > footerTop - editorHeight) {
+ // Snap editor to the bottom of the row container
+ newTop = footerTop - editorHeight - gridTop;
+ }
+
+ editorOverlay.getStyle().setTop(newTop, Unit.PX);
}
protected void setGridEnabled(boolean enabled) {
@@ -1777,6 +2103,44 @@ public class Grid<T> extends ResizeComposite implements
public boolean isEditorColumnError(Column<?, T> column) {
return columnErrors.contains(column);
}
+
+ public void setBuffered(boolean buffered) {
+ this.buffered = buffered;
+ setMessageAndButtonsWrapperVisible(buffered);
+ }
+
+ public boolean isBuffered() {
+ return buffered;
+ }
+
+ private void setMessageAndButtonsWrapperVisible(boolean visible) {
+ if (visible) {
+ messageAndButtonsWrapper.getStyle().clearDisplay();
+ } else {
+ messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ /**
+ * Sets the event handler for this Editor.
+ *
+ * @since
+ * @param handler
+ * the new event handler
+ */
+ public void setEventHandler(EventHandler<T> handler) {
+ eventHandler = handler;
+ }
+
+ /**
+ * Returns the event handler of this Editor.
+ *
+ * @since
+ * @return the current event handler
+ */
+ public EventHandler<T> getEventHandler() {
+ return eventHandler;
+ }
}
public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
@@ -2351,6 +2715,7 @@ public class Grid<T> extends ResizeComposite implements
private boolean initDone = false;
private boolean selected = false;
+ private CheckBox selectAllCheckBox;
SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
super(selectColumnRenderer);
@@ -2375,41 +2740,57 @@ public class Grid<T> extends ResizeComposite implements
* exist.
*/
final SelectionModel.Multi<T> model = (Multi<T>) getSelectionModel();
- final CheckBox checkBox = GWT.create(CheckBox.class);
- checkBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
- @Override
- public void onValueChange(ValueChangeEvent<Boolean> event) {
- if (event.getValue()) {
- fireEvent(new SelectAllEvent<T>(model));
- selected = true;
- } else {
- model.deselectAll();
- selected = false;
- }
- }
- });
- checkBox.setValue(selected);
- selectionCell.setWidget(checkBox);
- // Select all with space when "select all" cell is active
- addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
- @Override
- public void onKeyUp(GridKeyUpEvent event) {
- if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) {
- return;
- }
- HeaderRow targetHeaderRow = getHeader().getRow(
- event.getFocusedCell().getRowIndex());
- if (!targetHeaderRow.isDefault()) {
- return;
+ if (selectAllCheckBox == null) {
+ selectAllCheckBox = GWT.create(CheckBox.class);
+ selectAllCheckBox.setStylePrimaryName(getStylePrimaryName()
+ + SELECT_ALL_CHECKBOX_CLASSNAME);
+ selectAllCheckBox
+ .addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+
+ @Override
+ public void onValueChange(
+ ValueChangeEvent<Boolean> event) {
+ if (event.getValue()) {
+ fireEvent(new SelectAllEvent<T>(model));
+ selected = true;
+ } else {
+ model.deselectAll();
+ selected = false;
+ }
+ }
+ });
+ selectAllCheckBox.setValue(selected);
+
+ // Select all with space when "select all" cell is active
+ addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
+ @Override
+ public void onKeyUp(GridKeyUpEvent event) {
+ if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) {
+ return;
+ }
+ HeaderRow targetHeaderRow = getHeader().getRow(
+ event.getFocusedCell().getRowIndex());
+ if (!targetHeaderRow.isDefault()) {
+ return;
+ }
+ if (event.getFocusedCell().getColumn() == SelectionColumn.this) {
+ // Send events to ensure state is updated
+ selectAllCheckBox.setValue(
+ !selectAllCheckBox.getValue(), true);
+ }
}
- if (event.getFocusedCell().getColumn() == SelectionColumn.this) {
- // Send events to ensure row selection state is
- // updated
- checkBox.setValue(!checkBox.getValue(), true);
+ });
+ } else {
+ for (HeaderRow row : header.getRows()) {
+ if (row.getCell(this).getType() == GridStaticCellType.WIDGET) {
+ // Detach from old header.
+ row.getCell(this).setText("");
}
}
- });
+ }
+
+ selectionCell.setWidget(selectAllCheckBox);
}
@Override
@@ -2471,7 +2852,6 @@ public class Grid<T> extends ResizeComposite implements
super.setEditable(editable);
return this;
}
-
}
/**
@@ -2736,7 +3116,9 @@ public class Grid<T> extends ResizeComposite implements
for (Column<?, T> column : visibleColumns) {
final double widthAsIs = column.getWidth();
final boolean isFixedWidth = widthAsIs >= 0;
- final double widthFixed = Math.max(widthAsIs,
+ // Check for max width just to be sure we don't break the limits
+ final double widthFixed = Math.max(
+ Math.min(getMaxWidth(column), widthAsIs),
column.getMinimumWidth());
defaultExpandRatios = defaultExpandRatios
&& (column.getExpandRatio() == -1 || column == selectionColumn);
@@ -2755,8 +3137,9 @@ public class Grid<T> extends ResizeComposite implements
for (Column<?, T> column : nonFixedColumns) {
final int expandRatio = (defaultExpandRatios ? 1 : column
.getExpandRatio());
- final double newWidth = column.getWidthActual();
final double maxWidth = getMaxWidth(column);
+ final double newWidth = Math.min(maxWidth,
+ column.getWidthActual());
boolean shouldExpand = newWidth < maxWidth && expandRatio > 0
&& column != selectionColumn;
if (shouldExpand) {
@@ -2775,6 +3158,10 @@ public class Grid<T> extends ResizeComposite implements
double pixelsToDistribute = escalator.getInnerWidth()
- reservedPixels;
if (pixelsToDistribute <= 0 || totalRatios <= 0) {
+ if (pixelsToDistribute <= 0) {
+ // Set column sizes for expanding columns
+ setColumnSizes(columnSizes);
+ }
return;
}
@@ -3222,7 +3609,6 @@ public class Grid<T> extends ResizeComposite implements
clickOutsideToCloseHandlerRegistration = Event
.addNativePreviewHandler(clickOutsideToCloseHandler);
}
- openCloseButton.setHeight("");
}
/**
@@ -3251,46 +3637,6 @@ public class Grid<T> extends ResizeComposite implements
return content.getParent() == rootContainer;
}
- /**
- * Adds or moves the given widget to the end of the sidebar.
- *
- * @param widget
- * the widget to add or move
- */
- public void add(Widget widget) {
- content.add(widget);
- updateVisibility();
- }
-
- /**
- * Removes the given widget from the sidebar.
- *
- * @param widget
- * the widget to remove
- */
- public void remove(Widget widget) {
- content.remove(widget);
- // updateVisibility is called by remove listener
- }
-
- /**
- * Inserts given widget to the given index inside the sidebar. If the
- * widget is already in the sidebar, then it is moved to the new index.
- * <p>
- * See
- * {@link FlowPanel#insert(com.google.gwt.user.client.ui.IsWidget, int)}
- * for further details.
- *
- * @param widget
- * the widget to insert
- * @param beforeIndex
- * 0-based index position for the widget.
- */
- public void insert(Widget widget, int beforeIndex) {
- content.insert(widget, beforeIndex);
- updateVisibility();
- }
-
@Override
public void setStylePrimaryName(String styleName) {
super.setStylePrimaryName(styleName);
@@ -3536,10 +3882,6 @@ public class Grid<T> extends ResizeComposite implements
private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator();
private boolean enabled = true;
- private double lastTouchEventTime = 0;
- private int lastTouchEventX = -1;
- private int lastTouchEventY = -1;
- private int lastTouchEventRow = -1;
private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater();
@@ -3725,8 +4067,8 @@ public class Grid<T> extends ResizeComposite implements
}
private boolean isSidebarOnDraggedRow() {
- return eventCell.getRowIndex() == 0 && getSidebar().isInDOM()
- && !getSidebar().isOpen();
+ return eventCell.getRowIndex() == 0 && sidebar.isInDOM()
+ && !sidebar.isOpen();
}
/**
@@ -3736,8 +4078,7 @@ public class Grid<T> extends ResizeComposite implements
private double getSidebarBoundaryComparedTo(double left) {
if (isSidebarOnDraggedRow()) {
double absoluteLeft = left + getElement().getAbsoluteLeft();
- double sidebarLeft = getSidebar().getElement()
- .getAbsoluteLeft();
+ double sidebarLeft = sidebar.getElement().getAbsoluteLeft();
double diff = absoluteLeft - sidebarLeft;
if (diff > 0) {
@@ -4236,6 +4577,16 @@ public class Grid<T> extends ResizeComposite implements
return this;
}
+ /**
+ * Returns the current header caption for this column
+ *
+ * @since
+ * @return the header caption string
+ */
+ public String getHeaderCaption() {
+ return headerCaption;
+ }
+
private void updateHeader() {
HeaderRow row = grid.getHeader().getDefaultRow();
if (row != null) {
@@ -4937,7 +5288,7 @@ public class Grid<T> extends ResizeComposite implements
@Override
public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
for (FlyweightCell cell : cellsToDetach) {
- Renderer renderer = findRenderer(cell);
+ Renderer<?> renderer = findRenderer(cell);
if (renderer instanceof WidgetRenderer) {
try {
Widget w = WidgetUtil.findWidget(cell.getElement()
@@ -4967,7 +5318,7 @@ public class Grid<T> extends ResizeComposite implements
// any more
rowReference.set(rowIndex, null, row.getElement());
for (FlyweightCell cell : detachedCells) {
- Renderer renderer = findRenderer(cell);
+ Renderer<?> renderer = findRenderer(cell);
if (renderer instanceof ComplexRenderer) {
try {
Column<?, T> column = getVisibleColumn(cell.getColumn());
@@ -5211,7 +5562,7 @@ public class Grid<T> extends ResizeComposite implements
sinkEvents(getHeader().getConsumedEvents());
sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP,
BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK,
- BrowserEvents.MOUSEDOWN));
+ BrowserEvents.MOUSEDOWN, BrowserEvents.CLICK));
// Make ENTER and SHIFT+ENTER in the header perform sorting
addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
@@ -5353,6 +5704,35 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Try to focus a cell by row and column index. Purely internal method.
+ *
+ * @param rowIndex
+ * row index from Editor
+ * @param columnIndex
+ * column index from Editor
+ */
+ void focusCell(int rowIndex, int columnIndex) {
+ RowReference<T> row = new RowReference<T>(this);
+ Range visibleRows = escalator.getVisibleRowRange();
+
+ TableRowElement rowElement;
+ if (visibleRows.contains(rowIndex)) {
+ rowElement = escalator.getBody().getRowElement(rowIndex);
+ } else {
+ getLogger().warning("Row index was not among visible row range");
+ return;
+ }
+ row.set(rowIndex, dataSource.getRow(rowIndex), rowElement);
+
+ CellReference<T> cell = new CellReference<T>(row);
+ cell.set(columnIndex, columnIndex, getVisibleColumn(columnIndex));
+
+ cellFocusHandler.setCellFocus(cell);
+
+ WidgetUtil.focus(getElement());
+ }
+
+ /**
* Refreshes all header rows
*/
void refreshHeader() {
@@ -5883,10 +6263,23 @@ public class Grid<T> extends ResizeComposite implements
return footer.isVisible();
}
- protected Editor<T> getEditor() {
+ public Editor<T> getEditor() {
return editor;
}
+ /**
+ * Add handler for editor open/move/close events
+ *
+ * @param handler
+ * editor handler object
+ * @return a {@link HandlerRegistration} object that can be used to remove
+ * the event handler
+ */
+ public HandlerRegistration addEditorEventHandler(EditorEventHandler handler) {
+ return addHandler(handler, EditorEvent.TYPE);
+
+ }
+
protected Escalator getEscalator() {
return escalator;
}
@@ -6051,7 +6444,12 @@ public class Grid<T> extends ResizeComposite implements
}
private void updateFrozenColumns() {
- int numberOfColumns = frozenColumnCount;
+ escalator.getColumnConfiguration().setFrozenColumnCount(
+ getVisibleFrozenColumnCount());
+ }
+
+ private int getVisibleFrozenColumnCount() {
+ int numberOfColumns = getFrozenColumnCount();
// for the escalator the hidden columns are not in the frozen column
// count, but for grid they are. thus need to convert the index
@@ -6066,9 +6464,7 @@ public class Grid<T> extends ResizeComposite implements
} else if (selectionColumn != null) {
numberOfColumns++;
}
-
- escalator.getColumnConfiguration()
- .setFrozenColumnCount(numberOfColumns);
+ return numberOfColumns;
}
/**
@@ -6347,6 +6743,14 @@ public class Grid<T> extends ResizeComposite implements
return;
}
+ String eventType = event.getType();
+
+ if (eventType.equals(BrowserEvents.FOCUS)
+ || eventType.equals(BrowserEvents.BLUR)) {
+ super.onBrowserEvent(event);
+ return;
+ }
+
EventTarget target = event.getEventTarget();
if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) {
@@ -6357,7 +6761,6 @@ public class Grid<T> extends ResizeComposite implements
RowContainer container = escalator.findRowContainer(e);
Cell cell;
- String eventType = event.getType();
if (container == null) {
if (eventType.equals(BrowserEvents.KEYDOWN)
|| eventType.equals(BrowserEvents.KEYUP)
@@ -6387,7 +6790,7 @@ public class Grid<T> extends ResizeComposite implements
eventCell.set(cell, getSectionFromContainer(container));
// Editor can steal focus from Grid and is still handled
- if (handleEditorEvent(event, container)) {
+ if (isEditorEnabled() && handleEditorEvent(event, container)) {
return;
}
@@ -6465,50 +6868,8 @@ public class Grid<T> extends ResizeComposite implements
}
private boolean handleEditorEvent(Event event, RowContainer container) {
-
- final boolean closeEvent = event.getTypeInt() == Event.ONKEYDOWN
- && event.getKeyCode() == Editor.KEYCODE_HIDE;
-
- double now = Duration.currentTimeMillis();
- int currentX = WidgetUtil.getTouchOrMouseClientX(event);
- int currentY = WidgetUtil.getTouchOrMouseClientY(event);
-
- final boolean validTouchOpenEvent = event.getTypeInt() == Event.ONTOUCHEND
- && now - lastTouchEventTime < 500
- && lastTouchEventRow == eventCell.getRowIndex()
- && Math.abs(lastTouchEventX - currentX) < 20
- && Math.abs(lastTouchEventY - currentY) < 20;
-
- final boolean openEvent = event.getTypeInt() == Event.ONDBLCLICK
- || (event.getTypeInt() == Event.ONKEYDOWN && event.getKeyCode() == Editor.KEYCODE_SHOW)
- || validTouchOpenEvent;
-
- if (event.getTypeInt() == Event.ONTOUCHSTART) {
- lastTouchEventX = currentX;
- lastTouchEventY = currentY;
- }
-
- if (event.getTypeInt() == Event.ONTOUCHEND) {
- lastTouchEventTime = now;
- lastTouchEventRow = eventCell.getRowIndex();
- }
-
- if (editor.getState() != Editor.State.INACTIVE) {
- if (closeEvent) {
- editor.cancel();
- FocusUtil.setFocus(this, true);
- }
- return true;
- }
-
- if (container == escalator.getBody() && editor.isEnabled() && openEvent) {
- editor.editRow(eventCell.getRowIndex(),
- eventCell.getColumnIndexDOM());
- event.preventDefault();
- return true;
- }
-
- return false;
+ return getEditor().getEventHandler().handleEvent(
+ new EditorDomEvent<T>(event, getEventCell()));
}
private boolean handleRendererEvent(Event event, RowContainer container) {
@@ -6703,11 +7064,21 @@ public class Grid<T> extends ResizeComposite implements
if (args.getIndicesLength() == 0) {
return editor.editorOverlay;
- } else if (args.getIndicesLength() == 1
- && args.getIndex(0) < columns.size()) {
- escalator
- .scrollToColumn(args.getIndex(0), ScrollDestination.ANY, 0);
- return editor.getWidget(columns.get(args.getIndex(0))).getElement();
+ } else if (args.getIndicesLength() == 1) {
+ int index = args.getIndex(0);
+ if (index >= columns.size()) {
+ return null;
+ }
+
+ escalator.scrollToColumn(index, ScrollDestination.ANY, 0);
+ Widget widget = editor.getWidget(columns.get(index));
+
+ if (widget != null) {
+ return widget.getElement();
+ }
+
+ // No widget for the column.
+ return null;
}
return null;
@@ -6863,12 +7234,12 @@ public class Grid<T> extends ResizeComposite implements
* if the current selection model is not an instance of
* {@link SelectionModel.Single} or {@link SelectionModel.Multi}
*/
- @SuppressWarnings("unchecked")
public boolean select(T row) {
if (selectionModel instanceof SelectionModel.Single<?>) {
return ((SelectionModel.Single<T>) selectionModel).select(row);
} else if (selectionModel instanceof SelectionModel.Multi<?>) {
- return ((SelectionModel.Multi<T>) selectionModel).select(row);
+ return ((SelectionModel.Multi<T>) selectionModel)
+ .select(Collections.singleton(row));
} else {
throw new IllegalStateException("Unsupported selection model");
}
@@ -6888,12 +7259,12 @@ public class Grid<T> extends ResizeComposite implements
* if the current selection model is not an instance of
* {@link SelectionModel.Single} or {@link SelectionModel.Multi}
*/
- @SuppressWarnings("unchecked")
public boolean deselect(T row) {
if (selectionModel instanceof SelectionModel.Single<?>) {
return ((SelectionModel.Single<T>) selectionModel).deselect(row);
} else if (selectionModel instanceof SelectionModel.Multi<?>) {
- return ((SelectionModel.Multi<T>) selectionModel).deselect(row);
+ return ((SelectionModel.Multi<T>) selectionModel)
+ .deselect(Collections.singleton(row));
} else {
throw new IllegalStateException("Unsupported selection model");
}
@@ -7670,6 +8041,12 @@ public class Grid<T> extends ResizeComposite implements
if (escalator.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) {
recalculateColumnWidths();
}
+
+ // Vertical resizing could make editor positioning invalid so it
+ // needs to be recalculated on resize
+ if (isEditorActive()) {
+ editor.updateVerticalScrollPosition();
+ }
}
});
}
@@ -7775,15 +8152,15 @@ public class Grid<T> extends ResizeComposite implements
@Override
protected void doAttachChildren() {
- if (getSidebar().getParent() == this) {
- onAttach(getSidebar());
+ if (sidebar.getParent() == this) {
+ onAttach(sidebar);
}
}
@Override
protected void doDetachChildren() {
- if (getSidebar().getParent() == this) {
- onDetach(getSidebar());
+ if (sidebar.getParent() == this) {
+ onDetach(sidebar);
}
}
@@ -7815,6 +8192,10 @@ public class Grid<T> extends ResizeComposite implements
"Details generator may not be null");
}
+ for (Integer index : visibleDetails) {
+ setDetailsVisible(index, false);
+ }
+
this.detailsGenerator = detailsGenerator;
// this will refresh all visible spacers
@@ -7846,6 +8227,10 @@ public class Grid<T> extends ResizeComposite implements
* @see #isDetailsVisible(int)
*/
public void setDetailsVisible(int rowIndex, boolean visible) {
+ if (DetailsGenerator.NULL.equals(detailsGenerator)) {
+ return;
+ }
+
Integer rowIndexInteger = Integer.valueOf(rowIndex);
/*
@@ -7904,19 +8289,6 @@ public class Grid<T> extends ResizeComposite implements
}
/**
- * Returns the sidebar for this grid.
- * <p>
- * The grid's sidebar shows the column hiding options for those columns that
- * have been set as {@link Column#setHidable(boolean) hidable}.
- *
- * @since 7.5.0
- * @return the sidebar widget for this grid
- */
- private Sidebar getSidebar() {
- return sidebar;
- }
-
- /**
* Gets the customizable menu bar that is by default used for toggling
* column hidability. The application developer is allowed to add their
* custom items to the end of the menu, but should try to avoid modifying
@@ -7962,6 +8334,54 @@ public class Grid<T> extends ResizeComposite implements
}
}
+ @Override
+ public int getTabIndex() {
+ return FocusUtil.getTabIndex(this);
+ }
+
+ @Override
+ public void setAccessKey(char key) {
+ FocusUtil.setAccessKey(this, key);
+ }
+
+ @Override
+ public void setFocus(boolean focused) {
+ FocusUtil.setFocus(this, focused);
+ }
+
+ @Override
+ public void setTabIndex(int index) {
+ FocusUtil.setTabIndex(this, index);
+ }
+
+ @Override
+ public void focus() {
+ setFocus(true);
+ }
+
+ /**
+ * Sets the buffered editor mode.
+ *
+ * @since 7.6
+ * @param editorUnbuffered
+ * <code>true</code> to enable buffered editor,
+ * <code>false</code> to disable it
+ */
+ public void setEditorBuffered(boolean editorBuffered) {
+ editor.setBuffered(editorBuffered);
+ }
+
+ /**
+ * Gets the buffered editor mode.
+ *
+ * @since 7.6
+ * @return <code>true</code> if buffered editor is enabled,
+ * <code>false</code> otherwise
+ */
+ public boolean isEditorBuffered() {
+ return editor.isBuffered();
+ }
+
/**
* Returns the {@link EventCellReference} for the latest event fired from
* this Grid.
@@ -7974,4 +8394,26 @@ public class Grid<T> extends ResizeComposite implements
public EventCellReference<T> getEventCell() {
return eventCell;
}
+
+ /**
+ * Returns a CellReference for the cell to which the given element belongs
+ * to.
+ *
+ * @since 7.6
+ * @param element
+ * Element to find from the cell's content.
+ * @return CellReference or <code>null</code> if cell was not found.
+ */
+ public CellReference<T> getCellReference(Element element) {
+ RowContainer container = getEscalator().findRowContainer(element);
+ if (container != null) {
+ Cell cell = container.getCell(element);
+ if (cell != null) {
+ EventCellReference<T> cellRef = new EventCellReference<T>(this);
+ cellRef.set(cell, getSectionFromContainer(container));
+ return cellRef;
+ }
+ }
+ return null;
+ }
}
diff --git a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java
index 62b727e5f5..24bf9b6558 100644
--- a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java
+++ b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java
@@ -56,6 +56,8 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
private static final String ANDROID_MOTOROLA_3_0 = "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13";
private static final String ANDROID_GALAXY_NEXUS_4_0_4_CHROME = "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19";
+ private static final String EDGE_WINDOWS_10 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240";
+
public void testSafari3() {
VBrowserDetails bd = new VBrowserDetails(SAFARI3_WINDOWS);
assertWebKit(bd);
@@ -423,6 +425,14 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
assertWindows(bd, true);
}
+ public void testEdgeWindows10() {
+ VBrowserDetails bd = new VBrowserDetails(EDGE_WINDOWS_10);
+ assertEdge(bd);
+ assertBrowserMajorVersion(bd, 12);
+ assertBrowserMinorVersion(bd, 10240);
+ assertWindows(bd, false);
+ }
+
/*
* Helper methods below
*/
@@ -484,6 +494,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
assertFalse(browserDetails.isIE());
assertFalse(browserDetails.isOpera());
assertFalse(browserDetails.isSafari());
+ assertFalse(browserDetails.isEdge());
}
private void assertChrome(VBrowserDetails browserDetails) {
@@ -493,6 +504,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
assertFalse(browserDetails.isIE());
assertFalse(browserDetails.isOpera());
assertFalse(browserDetails.isSafari());
+ assertFalse(browserDetails.isEdge());
}
private void assertIE(VBrowserDetails browserDetails) {
@@ -502,6 +514,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
assertTrue(browserDetails.isIE());
assertFalse(browserDetails.isOpera());
assertFalse(browserDetails.isSafari());
+ assertFalse(browserDetails.isEdge());
}
private void assertOpera(VBrowserDetails browserDetails) {
@@ -511,6 +524,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
assertFalse(browserDetails.isIE());
assertTrue(browserDetails.isOpera());
assertFalse(browserDetails.isSafari());
+ assertFalse(browserDetails.isEdge());
}
private void assertSafari(VBrowserDetails browserDetails) {
@@ -520,6 +534,17 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase {
assertFalse(browserDetails.isIE());
assertFalse(browserDetails.isOpera());
assertTrue(browserDetails.isSafari());
+ assertFalse(browserDetails.isEdge());
+ }
+
+ private void assertEdge(VBrowserDetails browserDetails) {
+ // Browser
+ assertFalse(browserDetails.isFirefox());
+ assertFalse(browserDetails.isChrome());
+ assertFalse(browserDetails.isIE());
+ assertFalse(browserDetails.isOpera());
+ assertFalse(browserDetails.isSafari());
+ assertTrue(browserDetails.isEdge());
}
private void assertMacOSX(VBrowserDetails browserDetails) {