summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenri Sara <hesara@vaadin.com>2015-08-20 10:54:03 +0300
committerHenri Sara <hesara@vaadin.com>2015-08-20 10:54:32 +0300
commit2a26dc1d12ea9c201569e45148626220103ec980 (patch)
tree0b7e1fd276acca7cbdc4ea394f6f55023f960349
parentbbf0e1749499194aff4bf827bba6aef8b0752c3c (diff)
parentd3ccbfc53b4e0bdd405007b5a1432d61e31bf0c0 (diff)
downloadvaadin-framework-2a26dc1d12ea9c201569e45148626220103ec980.tar.gz
vaadin-framework-2a26dc1d12ea9c201569e45148626220103ec980.zip
Merge branch 'grid-unbuffered-editor' to master
Change-Id: I89fad77de62a5ca3c4a134a623aa86d8725e9bba
-rw-r--r--WebContent/VAADIN/themes/base/grid/grid.scss4
-rw-r--r--WebContent/VAADIN/themes/valo/components/_grid.scss4
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java10
-rw-r--r--client/src/com/vaadin/client/EventHelper.java17
-rw-r--r--client/src/com/vaadin/client/connectors/GridConnector.java107
-rw-r--r--client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java87
-rw-r--r--client/src/com/vaadin/client/ui/VButton.java3
-rw-r--r--client/src/com/vaadin/client/ui/button/ButtonConnector.java38
-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/ui/UIConnector.java18
-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/widgets/Grid.java388
-rw-r--r--server/src/com/vaadin/data/DataGenerator.java49
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java231
-rw-r--r--server/src/com/vaadin/ui/AbstractFocusable.java134
-rw-r--r--server/src/com/vaadin/ui/Button.java105
-rw-r--r--server/src/com/vaadin/ui/Grid.java579
-rw-r--r--server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java26
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridConstants.java15
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java25
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridState.java28
-rw-r--r--uitest/src/com/vaadin/tests/components/AbstractComponentTest.java41
-rw-r--r--uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java11
-rw-r--r--uitest/src/com/vaadin/tests/components/button/Buttons2.java3
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java211
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java74
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java2
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java264
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java297
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java223
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java79
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java2
38 files changed, 2647 insertions, 800 deletions
diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss
index f587dfef4f..6b3b017070 100644
--- a/WebContent/VAADIN/themes/base/grid/grid.scss
+++ b/WebContent/VAADIN/themes/base/grid/grid.scss
@@ -396,6 +396,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co
min-width: 100%;
max-width: 100%;
}
+
+ &.not-editable.#{$primaryStyleName}-cell {
+ float: none;
+ }
}
.error::before {
diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss
index dffd5fbb65..e9b4d249c7 100644
--- a/WebContent/VAADIN/themes/valo/components/_grid.scss
+++ b/WebContent/VAADIN/themes/valo/components/_grid.scss
@@ -124,6 +124,10 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default;
vertical-align: middle;
}
+ &.not-editable.#{$primary-stylename}-cell {
+ float: none;
+ }
+
.error::before {
border-top: round($v-unit-size / 4) solid $v-error-indicator-color;
border-right: round($v-unit-size / 4) solid transparent;
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index 628559dd2a..d9e705426b 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -94,7 +94,6 @@ import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.dd.VDragAndDropManager;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.client.ui.window.WindowConnector;
-import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.VaadinUriResolver;
@@ -3378,20 +3377,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/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/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java
index 15acbc0d5a..74f8a96bc0 100644
--- a/client/src/com/vaadin/client/connectors/GridConnector.java
+++ b/client/src/com/vaadin/client/connectors/GridConnector.java
@@ -31,6 +31,7 @@ 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.user.client.Timer;
import com.google.gwt.user.client.ui.CheckBox;
@@ -39,8 +40,8 @@ import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.DeferredWorker;
import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.TooltipInfo;
import com.vaadin.client.ServerConnector;
-import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener;
import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
@@ -48,6 +49,7 @@ 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;
@@ -61,6 +63,10 @@ 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;
@@ -114,8 +120,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 +147,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 +156,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
return null;
}
}
-
}
/**
@@ -579,6 +580,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
private String lastKnownTheme = null;
private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator();
+ private final CustomStyleGenerator styleGenerator = new CustomStyleGenerator();
private final DetailsListener detailsListener = new DetailsListener() {
@Override
@@ -710,6 +712,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements
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) {
@@ -747,9 +753,38 @@ public class GridConnector extends AbstractHasComponentsConnector implements
getWidget().addColumnReorderHandler(columnReorderHandler);
getWidget().addColumnVisibilityChangeHandler(
columnVisibilityChangeHandler);
+
+ ConnectorFocusAndBlurHandler.addHandlers(this);
+
getWidget().setDetailsGenerator(customDetailsGenerator);
getLayoutManager().registerDependency(this, getWidget().getElement());
+ 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 onEditorMove(EditorMoveEvent e) {
+ if (hasEventListener(GridConstants.EDITOR_MOVE_EVENT_ID)) {
+ String rowKey = getRowKey((JsonObject) e.getRow());
+ getRpcProxy(GridServerRpc.class).editorMove(rowKey);
+ }
+ }
+
+ @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();
}
@@ -1099,24 +1134,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
}
- @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;
@@ -1271,4 +1288,42 @@ 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);
+ }
}
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/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/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/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/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index 6d98e2108a..bcd90437a4 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;
@@ -320,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) {
@@ -341,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());
}
}
});
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/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index 369e530b9d..eb54f9c720 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -42,6 +42,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;
@@ -79,6 +80,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;
@@ -121,6 +123,11 @@ 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.EditorMoveEvent;
+import com.vaadin.client.widget.grid.events.EditorOpenEvent;
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 +213,8 @@ 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";
@@ -1157,8 +1164,17 @@ public class Grid<T> extends ResizeComposite implements
private int columnIndex = -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;
@@ -1213,6 +1229,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) {
@@ -1220,10 +1237,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();
}
}
@@ -1231,22 +1245,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();
@@ -1277,6 +1299,10 @@ 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);
+ }
}
public int getRow() {
@@ -1284,12 +1310,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);
+ }
}
/**
@@ -1306,28 +1346,40 @@ 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;
-
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);
}
}
@@ -1350,14 +1402,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;
+ columnIndex = -1;
+ grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
updateSelectionCheckboxesAsNeeded(true);
+ grid.fireEvent(new EditorCloseEvent(grid.eventCell));
}
private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) {
@@ -1450,14 +1506,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);
}
}
@@ -1467,15 +1524,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() {
@@ -1520,6 +1568,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());
@@ -1530,6 +1580,9 @@ public class Grid<T> extends ResizeComposite implements
@Override
public void onScroll(ScrollEvent event) {
updateHorizontalScrollPosition();
+ if (!isBuffered()) {
+ updateVerticalScrollPosition();
+ }
}
});
@@ -1577,6 +1630,50 @@ public class Grid<T> extends ResizeComposite implements
}
} else {
cell.addClassName(NOT_EDITABLE_CLASS_NAME);
+ cell.addClassName(tr.getCells().getItem(i).getClassName());
+ // If the focused 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);
+
+ 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());
+ }
}
}
@@ -1590,8 +1687,12 @@ public class Grid<T> extends ResizeComposite implements
messageAndButtonsWrapper.appendChild(buttonsWrapper);
}
- attachWidget(saveButton, buttonsWrapper);
- attachWidget(cancelButton, buttonsWrapper);
+ if (isBuffered()) {
+ attachWidget(saveButton, buttonsWrapper);
+ attachWidget(cancelButton, buttonsWrapper);
+ }
+
+ setMessageAndButtonsWrapperVisible(isBuffered());
updateHorizontalScrollPosition();
@@ -1603,9 +1704,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
@@ -1638,6 +1741,15 @@ public class Grid<T> extends ResizeComposite implements
}
protected void hideOverlay() {
+ if (editorOverlay.getParentElement() == null) {
+ return;
+ }
+
+ if (pinnedRowHandle != null) {
+ pinnedRowHandle.unpin();
+ pinnedRowHandle = null;
+ }
+
for (Widget w : columnToWidget.values()) {
setParent(w, null);
}
@@ -1729,6 +1841,35 @@ public class Grid<T> extends ResizeComposite implements
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) {
// TODO: This should be informed to handler as well so possible
// fields can be disabled.
@@ -1805,6 +1946,23 @@ 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);
+ }
+ }
}
public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
@@ -5254,7 +5412,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() {
@@ -5930,6 +6088,19 @@ public class Grid<T> extends ResizeComposite implements
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;
}
@@ -6393,6 +6564,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))) {
@@ -6403,7 +6582,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)
@@ -6511,50 +6689,68 @@ 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;
+ final int type = event.getTypeInt();
+ final int key = event.getKeyCode();
+ final boolean editorIsActive = editor.getState() != Editor.State.INACTIVE;
double now = Duration.currentTimeMillis();
int currentX = WidgetUtil.getTouchOrMouseClientX(event);
int currentY = WidgetUtil.getTouchOrMouseClientY(event);
- final boolean validTouchOpenEvent = event.getTypeInt() == Event.ONTOUCHEND
+ final boolean validTouchOpenEvent = type == 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;
+ final boolean openEvent = eventCell.isBody()
+ && (type == Event.ONDBLCLICK
+ || (type == Event.ONKEYDOWN && key == Editor.KEYCODE_SHOW) || validTouchOpenEvent);
- if (event.getTypeInt() == Event.ONTOUCHSTART) {
+ if (type == Event.ONTOUCHSTART) {
lastTouchEventX = currentX;
lastTouchEventY = currentY;
}
- if (event.getTypeInt() == Event.ONTOUCHEND) {
+ if (type == Event.ONTOUCHEND) {
lastTouchEventTime = now;
lastTouchEventRow = eventCell.getRowIndex();
}
- if (editor.getState() != Editor.State.INACTIVE) {
- if (closeEvent) {
- editor.cancel();
- FocusUtil.setFocus(this, true);
- }
- return true;
- }
+ // TODO: Move on touch events
+ final boolean moveEvent = eventCell.isBody() && type == Event.ONCLICK;
+
+ final boolean closeEvent = type == Event.ONKEYDOWN
+ && key == Editor.KEYCODE_HIDE;
+
+ if (!editorIsActive && editor.isEnabled() && openEvent) {
- if (container == escalator.getBody() && editor.isEnabled() && openEvent) {
editor.editRow(eventCell.getRowIndex(),
eventCell.getColumnIndexDOM());
+ fireEvent(new EditorOpenEvent(eventCell));
event.preventDefault();
+
+ return true;
+
+ } else if (editorIsActive && !editor.isBuffered() && moveEvent) {
+
+ cellFocusHandler.setCellFocus(eventCell);
+ editor.editRow(eventCell.getRowIndex(),
+ eventCell.getColumnIndexDOM());
+ fireEvent(new EditorMoveEvent(eventCell));
+
+ return true;
+
+ } else if (editorIsActive && closeEvent) {
+
+ editor.cancel();
+ FocusUtil.setFocus(this, true);
+
return true;
}
- return false;
+ // Swallow events if editor is open and buffered (modal)
+ return editor.isBuffered() && editorIsActive;
}
private boolean handleRendererEvent(Event event, RowContainer container) {
@@ -7726,6 +7922,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();
+ }
}
});
}
@@ -8018,6 +8220,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.
@@ -8030,4 +8280,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
+ * @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/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java
new file mode 100644
index 0000000000..f025623a3e
--- /dev/null
+++ b/server/src/com/vaadin/data/DataGenerator.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.data;
+
+import java.io.Serializable;
+
+import com.vaadin.ui.Grid.AbstractGridExtension;
+import com.vaadin.ui.Grid.AbstractRenderer;
+
+import elemental.json.JsonObject;
+
+/**
+ * Interface for {@link AbstractGridExtension}s that allows adding data to row
+ * objects being sent to client by the {@link RpcDataProviderExtension}.
+ * <p>
+ * {@link AbstractRenderer} implements this interface to provide encoded data to
+ * client for {@link Renderer}s automatically.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface DataGenerator extends Serializable {
+
+ /**
+ * Adds data to row object for given item and item id being sent to client.
+ *
+ * @param itemId
+ * item id of item
+ * @param item
+ * item being sent to client
+ * @param rowData
+ * row object being sent to client
+ */
+ public void generateData(Object itemId, Item item, JsonObject rowData);
+
+}
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
index 848873098d..403cc4c016 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -24,11 +24,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import com.google.gwt.thirdparty.guava.common.collect.BiMap;
import com.google.gwt.thirdparty.guava.common.collect.HashBiMap;
@@ -44,10 +41,8 @@ import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Property.ValueChangeNotifier;
-import com.vaadin.data.util.converter.Converter;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.ClientConnector;
-import com.vaadin.server.KeyMapper;
import com.vaadin.shared.data.DataProviderRpc;
import com.vaadin.shared.data.DataRequestRpc;
import com.vaadin.shared.ui.grid.GridClientRpc;
@@ -55,18 +50,13 @@ import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.Range;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
-import com.vaadin.ui.Grid.CellReference;
-import com.vaadin.ui.Grid.CellStyleGenerator;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.Grid.DetailsGenerator;
import com.vaadin.ui.Grid.RowReference;
-import com.vaadin.ui.Grid.RowStyleGenerator;
-import com.vaadin.ui.renderers.Renderer;
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.JsonObject;
-import elemental.json.JsonValue;
/**
* Provides Vaadin server-side container data source to a
@@ -92,7 +82,7 @@ public class RpcDataProviderExtension extends AbstractExtension {
* itemId &lrarr; key mapping is not needed anymore. In other words, this
* doesn't leak memory.
*/
- public class DataProviderKeyMapper implements Serializable {
+ public class DataProviderKeyMapper implements Serializable, DataGenerator {
private final BiMap<Object, String> itemIdToKey = HashBiMap.create();
private Set<Object> pinnedItemIds = new HashSet<Object>();
private long rollingIndex = 0;
@@ -251,6 +241,16 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
/**
+ * {@inheritDoc}
+ *
+ * @since
+ */
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ rowData.put(GridState.JSONKEY_ROWKEY, getKey(itemId));
+ }
+
+ /**
* Removes all inactive item id to key mapping from the key mapper.
*
* @since
@@ -415,7 +415,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
* @since 7.5.0
* @author Vaadin Ltd
*/
- public static final class DetailComponentManager implements Serializable {
+ // TODO this should probably be a static nested class
+ public final class DetailComponentManager implements DataGenerator {
/**
* This map represents all the components that have been requested for
* each item id.
@@ -423,9 +424,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
* Normally this map is consistent with what is displayed in the
* component hierarchy (and thus the DOM). The only time this map is out
* of sync with the DOM is between the any calls to
- * {@link #createDetails(Object, int)} or
- * {@link #destroyDetails(Object)}, and
- * {@link GridClientRpc#setDetailsConnectorChanges(Set)}.
+ * {@link #createDetails(Object)} or {@link #destroyDetails(Object)},
+ * and {@link GridClientRpc#setDetailsConnectorChanges(Set)}.
* <p>
* This is easily checked: if {@link #unattachedComponents} is
* {@link Collection#isEmpty() empty}, then this field is consistent
@@ -436,7 +436,7 @@ public class RpcDataProviderExtension extends AbstractExtension {
/**
* Keeps tabs on all the details that did not get a component during
- * {@link #createDetails(Object, int)}.
+ * {@link #createDetails(Object)}.
*/
private final Set<Object> emptyDetails = Sets.newHashSet();
@@ -541,6 +541,25 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
this.grid = grid;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since
+ */
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ if (visibleDetails.contains(itemId)) {
+ // Double check to be sure details component exists.
+ detailComponentManager.createDetails(itemId);
+ Component detailsComponent = visibleDetailsComponents
+ .get(itemId);
+ rowData.put(
+ GridState.JSONKEY_DETAILS_VISIBLE,
+ (detailsComponent != null ? detailsComponent
+ .getConnectorId() : ""));
+ }
+ }
}
private final Indexed container;
@@ -618,14 +637,9 @@ public class RpcDataProviderExtension extends AbstractExtension {
private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper();
- private KeyMapper<Object> columnKeys;
-
/** RpcDataProvider should send the current cache again. */
private boolean refreshCache = false;
- private RowReference rowReference;
- private CellReference cellReference;
-
/** Set of updated item ids */
private Set<Object> updatedItemIds = new LinkedHashSet<Object>();
@@ -642,9 +656,13 @@ public class RpcDataProviderExtension extends AbstractExtension {
* This map represents all the details that are user-defined as visible.
* This does not reflect the status in the DOM.
*/
- private Set<Object> visibleDetails = new HashSet<Object>();
+ // TODO this should probably be inside DetailComponentManager
+ private final Set<Object> visibleDetails = new HashSet<Object>();
private final DetailComponentManager detailComponentManager = new DetailComponentManager();
+
+ private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
+
private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();
/**
@@ -690,6 +708,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
.addItemSetChangeListener(itemListener);
}
+ addDataGenerator(keyMapper);
+ addDataGenerator(detailComponentManager);
}
/**
@@ -774,80 +794,14 @@ public class RpcDataProviderExtension extends AbstractExtension {
private JsonObject getRowData(Collection<Column> columns, Object itemId) {
Item item = container.getItem(itemId);
- JsonObject rowData = Json.createObject();
-
- Grid grid = getGrid();
-
- for (Column column : columns) {
- Object propertyId = column.getPropertyId();
-
- Object propertyValue = item.getItemProperty(propertyId).getValue();
- JsonValue encodedValue = encodeValue(propertyValue,
- column.getRenderer(), column.getConverter(),
- grid.getLocale());
-
- rowData.put(columnKeys.key(propertyId), encodedValue);
- }
-
final JsonObject rowObject = Json.createObject();
- rowObject.put(GridState.JSONKEY_DATA, rowData);
- rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId));
-
- if (visibleDetails.contains(itemId)) {
- // Double check to be sure details component exists.
- detailComponentManager.createDetails(itemId);
- Component detailsComponent = detailComponentManager.visibleDetailsComponents
- .get(itemId);
- rowObject.put(
- GridState.JSONKEY_DETAILS_VISIBLE,
- (detailsComponent != null ? detailsComponent
- .getConnectorId() : ""));
- }
-
- rowReference.set(itemId);
-
- CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator();
- if (cellStyleGenerator != null) {
- setGeneratedCellStyles(cellStyleGenerator, rowObject, columns);
- }
- RowStyleGenerator rowStyleGenerator = grid.getRowStyleGenerator();
- if (rowStyleGenerator != null) {
- setGeneratedRowStyles(rowStyleGenerator, rowObject);
+ for (DataGenerator dg : dataGenerators) {
+ dg.generateData(itemId, item, rowObject);
}
return rowObject;
}
- private void setGeneratedCellStyles(CellStyleGenerator generator,
- JsonObject rowObject, Collection<Column> columns) {
- JsonObject cellStyles = null;
- for (Column column : columns) {
- Object propertyId = column.getPropertyId();
- cellReference.set(propertyId);
- String style = generator.getStyle(cellReference);
- if (style != null && !style.isEmpty()) {
- if (cellStyles == null) {
- cellStyles = Json.createObject();
- }
-
- String columnKey = columnKeys.key(propertyId);
- cellStyles.put(columnKey, style);
- }
- }
- if (cellStyles != null) {
- rowObject.put(GridState.JSONKEY_CELLSTYLES, cellStyles);
- }
-
- }
-
- private void setGeneratedRowStyles(RowStyleGenerator generator,
- JsonObject rowObject) {
- String rowStyle = generator.getStyle(rowReference);
- if (rowStyle != null && !rowStyle.isEmpty()) {
- rowObject.put(GridState.JSONKEY_ROWSTYLE, rowStyle);
- }
- }
-
/**
* Makes the data source available to the given {@link Grid} component.
*
@@ -856,13 +810,38 @@ public class RpcDataProviderExtension extends AbstractExtension {
* @param columnKeys
* the key mapper for columns
*/
- public void extend(Grid component, KeyMapper<Object> columnKeys) {
- this.columnKeys = columnKeys;
+ public void extend(Grid component) {
detailComponentManager.setGrid(component);
super.extend(component);
}
/**
+ * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}.
+ * DataGenerators are called when sending row data to client. If given
+ * DataGenerator is already added, this method does nothing.
+ *
+ * @since
+ * @param generator
+ * generator to add
+ */
+ public void addDataGenerator(DataGenerator generator) {
+ dataGenerators.add(generator);
+ }
+
+ /**
+ * Removes a {@link DataGenerator} from this
+ * {@code RpcDataProviderExtension}. If given DataGenerator is not added to
+ * this data provider, this method does nothing.
+ *
+ * @since
+ * @param generator
+ * generator to remove
+ */
+ public void removeDataGenerator(DataGenerator generator) {
+ dataGenerators.remove(generator);
+ }
+
+ /**
* Informs the client side that new rows have been inserted into the data
* source.
*
@@ -960,11 +939,7 @@ public class RpcDataProviderExtension extends AbstractExtension {
.removeItemSetChangeListener(itemListener);
}
- } else if (parent instanceof Grid) {
- Grid grid = (Grid) parent;
- rowReference = new RowReference(grid);
- cellReference = new CellReference(rowReference);
- } else {
+ } else if (!(parent instanceof Grid)) {
throw new IllegalStateException(
"Grid is the only accepted parent type");
}
@@ -1013,68 +988,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
/**
- * Converts and encodes the given data model property value using the given
- * converter and renderer. This method is public only for testing purposes.
- *
- * @param renderer
- * the renderer to use
- * @param converter
- * the converter to use
- * @param modelValue
- * the value to convert and encode
- * @param locale
- * the locale to use in conversion
- * @return an encoded value ready to be sent to the client
- */
- public static <T> JsonValue encodeValue(Object modelValue,
- Renderer<T> renderer, Converter<?, ?> converter, Locale locale) {
- Class<T> presentationType = renderer.getPresentationType();
- T presentationValue;
-
- if (converter == null) {
- try {
- presentationValue = presentationType.cast(modelValue);
- } catch (ClassCastException e) {
- if (presentationType == String.class) {
- // If there is no converter, just fallback to using
- // toString().
- // modelValue can't be null as Class.cast(null) will always
- // succeed
- presentationValue = (T) modelValue.toString();
- } else {
- throw new Converter.ConversionException(
- "Unable to convert value of type "
- + modelValue.getClass().getName()
- + " to presentation type "
- + presentationType.getName()
- + ". No converter is set and the types are not compatible.");
- }
- }
- } else {
- assert presentationType.isAssignableFrom(converter
- .getPresentationType());
- @SuppressWarnings("unchecked")
- Converter<T, Object> safeConverter = (Converter<T, Object>) converter;
- presentationValue = safeConverter.convertToPresentation(modelValue,
- safeConverter.getPresentationType(), locale);
- }
-
- JsonValue encodedValue;
- try {
- encodedValue = renderer.encode(presentationValue);
- } catch (Exception e) {
- getLogger().log(Level.SEVERE, "Unable to encode data", e);
- encodedValue = renderer.encode(null);
- }
-
- return encodedValue;
- }
-
- private static Logger getLogger() {
- return Logger.getLogger(RpcDataProviderExtension.class.getName());
- }
-
- /**
* Marks a row's details to be visible or hidden.
* <p>
* If that row is currently in the client side's cache, this information
diff --git a/server/src/com/vaadin/ui/AbstractFocusable.java b/server/src/com/vaadin/ui/AbstractFocusable.java
new file mode 100644
index 0000000000..ad3b96f29b
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractFocusable.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ui;
+
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.FieldEvents.FocusNotifier;
+import com.vaadin.shared.ui.TabIndexState;
+import com.vaadin.ui.Component.Focusable;
+
+/**
+ * An abstract base class for focusable components. Includes API for setting the
+ * tab index, programmatic focusing, and adding focus and blur listeners.
+ *
+ * @since 7.6
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractFocusable extends AbstractComponent implements
+ Focusable, FocusNotifier, BlurNotifier {
+
+ protected AbstractFocusable() {
+ registerRpc(new FocusAndBlurServerRpcImpl(this) {
+ @Override
+ protected void fireEvent(Event event) {
+ AbstractFocusable.this.fireEvent(event);
+ }
+ });
+ }
+
+ @Override
+ public void addBlurListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)}
+ */
+ @Override
+ @Deprecated
+ public void addListener(BlurListener listener) {
+ addBlurListener(listener);
+ }
+
+ @Override
+ public void removeBlurListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeBlurListener(BlurListener)}
+ */
+ @Override
+ @Deprecated
+ public void removeListener(BlurListener listener) {
+ removeBlurListener(listener);
+
+ }
+
+ @Override
+ public void addFocusListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addFocusListener(FocusListener)}
+ */
+ @Override
+ @Deprecated
+ public void addListener(FocusListener listener) {
+ addFocusListener(listener);
+ }
+
+ @Override
+ public void removeFocusListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeFocusListener(FocusListener)}
+ */
+ @Override
+ @Deprecated
+ public void removeListener(FocusListener listener) {
+ removeFocusListener(listener);
+ }
+
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ @Override
+ public int getTabIndex() {
+ return getState(false).tabIndex;
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ getState().tabIndex = tabIndex;
+ }
+
+ @Override
+ protected TabIndexState getState() {
+ return (TabIndexState) super.getState();
+ }
+
+ @Override
+ protected TabIndexState getState(boolean markAsDirty) {
+ return (TabIndexState) super.getState(markAsDirty);
+ }
+}
diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java
index 6beb6ed686..a918780a60 100644
--- a/server/src/com/vaadin/ui/Button.java
+++ b/server/src/com/vaadin/ui/Button.java
@@ -24,12 +24,7 @@ import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import com.vaadin.event.Action;
-import com.vaadin.event.FieldEvents;
-import com.vaadin.event.FieldEvents.BlurEvent;
-import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
-import com.vaadin.event.FieldEvents.FocusEvent;
-import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutAction.ModifierKey;
@@ -38,7 +33,6 @@ import com.vaadin.server.Resource;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.button.ButtonServerRpc;
import com.vaadin.shared.ui.button.ButtonState;
-import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.util.ReflectTools;
@@ -50,8 +44,7 @@ import com.vaadin.util.ReflectTools;
* @since 3.0
*/
@SuppressWarnings("serial")
-public class Button extends AbstractComponent implements
- FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Focusable,
+public class Button extends AbstractFocusable implements
Action.ShortcutNotifier {
private ButtonServerRpc rpc = new ButtonServerRpc() {
@@ -72,20 +65,11 @@ public class Button extends AbstractComponent implements
}
};
- FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) {
-
- @Override
- protected void fireEvent(Event event) {
- Button.this.fireEvent(event);
- }
- };
-
/**
* Creates a new push button.
*/
public Button() {
registerRpc(rpc);
- registerRpc(focusBlurRpc);
}
/**
@@ -393,67 +377,6 @@ public class Button extends AbstractComponent implements
fireEvent(new Button.ClickEvent(this, details));
}
- @Override
- public void addBlurListener(BlurListener listener) {
- addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
- BlurListener.blurMethod);
- }
-
- /**
- * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)}
- **/
- @Override
- @Deprecated
- public void addListener(BlurListener listener) {
- addBlurListener(listener);
- }
-
- @Override
- public void removeBlurListener(BlurListener listener) {
- removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
- }
-
- /**
- * @deprecated As of 7.0, replaced by
- * {@link #removeBlurListener(BlurListener)}
- **/
- @Override
- @Deprecated
- public void removeListener(BlurListener listener) {
- removeBlurListener(listener);
- }
-
- @Override
- public void addFocusListener(FocusListener listener) {
- addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
- FocusListener.focusMethod);
- }
-
- /**
- * @deprecated As of 7.0, replaced by
- * {@link #addFocusListener(FocusListener)}
- **/
- @Override
- @Deprecated
- public void addListener(FocusListener listener) {
- addFocusListener(listener);
- }
-
- @Override
- public void removeFocusListener(FocusListener listener) {
- removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
- }
-
- /**
- * @deprecated As of 7.0, replaced by
- * {@link #removeFocusListener(FocusListener)}
- **/
- @Override
- @Deprecated
- public void removeListener(FocusListener listener) {
- removeFocusListener(listener);
- }
-
/*
* Actions
*/
@@ -575,32 +498,6 @@ public class Button extends AbstractComponent implements
getState().disableOnClick = disableOnClick;
}
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Component.Focusable#getTabIndex()
- */
- @Override
- public int getTabIndex() {
- return getState(false).tabIndex;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
- */
- @Override
- public void setTabIndex(int tabIndex) {
- getState().tabIndex = tabIndex;
- }
-
- @Override
- public void focus() {
- // Overridden only to make public
- super.focus();
- }
-
@Override
protected ButtonState getState() {
return (ButtonState) super.getState();
diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java
index 3e4a78db9d..c7ad9632fa 100644
--- a/server/src/com/vaadin/ui/Grid.java
+++ b/server/src/com/vaadin/ui/Grid.java
@@ -31,6 +31,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -45,10 +46,14 @@ import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView;
import com.vaadin.data.Container;
import com.vaadin.data.Container.Indexed;
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.Container.ItemSetChangeListener;
+import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Container.PropertySetChangeEvent;
import com.vaadin.data.Container.PropertySetChangeListener;
import com.vaadin.data.Container.PropertySetChangeNotifier;
import com.vaadin.data.Container.Sortable;
+import com.vaadin.data.DataGenerator;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.RpcDataProviderExtension;
@@ -172,7 +177,7 @@ import elemental.json.JsonValue;
* @since 7.4
* @author Vaadin Ltd
*/
-public class Grid extends AbstractComponent implements SelectionNotifier,
+public class Grid extends AbstractFocusable implements SelectionNotifier,
SortNotifier, SelectiveRenderer, ItemClickNotifier {
/**
@@ -511,6 +516,100 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
/**
+ * Interface for an editor event listener
+ */
+ public interface EditorListener extends Serializable {
+
+ public static final Method EDITOR_OPEN_METHOD = ReflectTools
+ .findMethod(EditorListener.class, "editorOpened",
+ EditorOpenEvent.class);
+ public static final Method EDITOR_MOVE_METHOD = ReflectTools
+ .findMethod(EditorListener.class, "editorMoved",
+ EditorMoveEvent.class);
+ public static final Method EDITOR_CLOSE_METHOD = ReflectTools
+ .findMethod(EditorListener.class, "editorClosed",
+ EditorCloseEvent.class);
+
+ /**
+ * Called when an editor is opened
+ *
+ * @param e
+ * an editor open event object
+ */
+ public void editorOpened(EditorOpenEvent e);
+
+ /**
+ * Called when an editor is reopened without closing it first
+ *
+ * @param e
+ * an editor move event object
+ */
+ public void editorMoved(EditorMoveEvent e);
+
+ /**
+ * Called when an editor is closed
+ *
+ * @param e
+ * an editor close event object
+ */
+ public void editorClosed(EditorCloseEvent e);
+
+ }
+
+ /**
+ * Base class for editor related events
+ */
+ public static abstract class EditorEvent extends Component.Event {
+
+ private Object itemID;
+
+ protected EditorEvent(Grid source, Object itemID) {
+ super(source);
+ this.itemID = itemID;
+ }
+
+ /**
+ * Get the item (row) for which this editor was opened
+ */
+ public Object getItem() {
+ return itemID;
+ }
+
+ }
+
+ /**
+ * This event gets fired when an editor is opened
+ */
+ public static class EditorOpenEvent extends EditorEvent {
+
+ public EditorOpenEvent(Grid source, Object itemID) {
+ super(source, itemID);
+ }
+ }
+
+ /**
+ * This event gets fired when an editor is opened while another row is being
+ * edited (i.e. editor focus moves elsewhere)
+ */
+ public static class EditorMoveEvent extends EditorEvent {
+
+ public EditorMoveEvent(Grid source, Object itemID) {
+ super(source, itemID);
+ }
+ }
+
+ /**
+ * This event gets fired when an editor is dismissed or closed by other
+ * means.
+ */
+ public static class EditorCloseEvent extends EditorEvent {
+
+ public EditorCloseEvent(Grid source, Object itemID) {
+ super(source, itemID);
+ }
+ }
+
+ /**
* Default error handler for the editor
*
*/
@@ -1432,39 +1531,174 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
/**
- * Callback interface for generating custom style names for data rows
+ * A callback interface for generating custom style names for Grid rows.
*
* @see Grid#setRowStyleGenerator(RowStyleGenerator)
*/
public interface RowStyleGenerator extends Serializable {
/**
- * Called by Grid to generate a style name for a row
+ * Called by Grid to generate a style name for a row.
*
- * @param rowReference
- * The row to generate a style for
+ * @param row
+ * the row to generate a style for
* @return the style name to add to this row, or {@code null} to not set
* any style
*/
- public String getStyle(RowReference rowReference);
+ public String getStyle(RowReference row);
}
/**
- * Callback interface for generating custom style names for cells
+ * A callback interface for generating custom style names for Grid cells.
*
* @see Grid#setCellStyleGenerator(CellStyleGenerator)
*/
public interface CellStyleGenerator extends Serializable {
/**
- * Called by Grid to generate a style name for a column
+ * Called by Grid to generate a style name for a column.
*
- * @param cellReference
- * The cell to generate a style for
+ * @param cell
+ * the cell to generate a style for
* @return the style name to add to this cell, or {@code null} to not
* set any style
*/
- public String getStyle(CellReference cellReference);
+ public String getStyle(CellReference cell);
+ }
+
+ /**
+ * A callback interface for generating optional descriptions (tooltips) for
+ * Grid rows. If a description is generated for a row, it is used for all
+ * the cells in the row for which a {@link CellDescriptionGenerator cell
+ * description} is not generated.
+ *
+ * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator)
+ *
+ * @since
+ */
+ public interface RowDescriptionGenerator extends Serializable {
+
+ /**
+ * Called by Grid to generate a description (tooltip) for a row. The
+ * description may contain HTML which is rendered directly; if this is
+ * not desired the returned string must be escaped by the implementing
+ * method.
+ *
+ * @param row
+ * the row to generate a description for
+ * @return the row description or {@code null} for no description
+ */
+ public String getDescription(RowReference row);
+ }
+
+ /**
+ * A callback interface for generating optional descriptions (tooltips) for
+ * Grid cells. If a cell has both a {@link RowDescriptionGenerator row
+ * description} and a cell description, the latter has precedence.
+ *
+ * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator)
+ *
+ * @since
+ */
+ public interface CellDescriptionGenerator extends Serializable {
+
+ /**
+ * Called by Grid to generate a description (tooltip) for a cell. The
+ * description may contain HTML which is rendered directly; if this is
+ * not desired the returned string must be escaped by the implementing
+ * method.
+ *
+ * @param cell
+ * the cell to generate a description for
+ * @return the cell description or {@code null} for no description
+ */
+ public String getDescription(CellReference cell);
+ }
+
+ /**
+ * Class for generating all row and cell related data for the essential
+ * parts of Grid.
+ */
+ private class RowDataGenerator implements DataGenerator {
+
+ private void put(String key, String value, JsonObject object) {
+ if (value != null && !value.isEmpty()) {
+ object.put(key, value);
+ }
+ }
+
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ RowReference row = new RowReference(Grid.this);
+ row.set(itemId);
+
+ if (rowStyleGenerator != null) {
+ String style = rowStyleGenerator.getStyle(row);
+ put(GridState.JSONKEY_ROWSTYLE, style, rowData);
+ }
+
+ if (rowDescriptionGenerator != null) {
+ String description = rowDescriptionGenerator
+ .getDescription(row);
+ put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData);
+
+ }
+
+ JsonObject cellStyles = Json.createObject();
+ JsonObject cellData = Json.createObject();
+ JsonObject cellDescriptions = Json.createObject();
+
+ CellReference cell = new CellReference(row);
+
+ for (Column column : getColumns()) {
+ cell.set(column.getPropertyId());
+
+ writeData(cell, cellData);
+ writeStyles(cell, cellStyles);
+ writeDescriptions(cell, cellDescriptions);
+ }
+
+ if (cellDescriptionGenerator != null
+ && cellDescriptions.keys().length > 0) {
+ rowData.put(GridState.JSONKEY_CELLDESCRIPTION, cellDescriptions);
+ }
+
+ if (cellStyleGenerator != null && cellStyles.keys().length > 0) {
+ rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles);
+ }
+
+ rowData.put(GridState.JSONKEY_DATA, cellData);
+ }
+
+ private void writeStyles(CellReference cell, JsonObject styles) {
+ if (cellStyleGenerator != null) {
+ String style = cellStyleGenerator.getStyle(cell);
+ put(columnKeys.key(cell.getPropertyId()), style, styles);
+ }
+ }
+
+ private void writeDescriptions(CellReference cell,
+ JsonObject descriptions) {
+ if (cellDescriptionGenerator != null) {
+ String description = cellDescriptionGenerator
+ .getDescription(cell);
+ put(columnKeys.key(cell.getPropertyId()), description,
+ descriptions);
+ }
+ }
+
+ private void writeData(CellReference cell, JsonObject data) {
+ Column column = getColumn(cell.getPropertyId());
+ Converter<?, ?> converter = column.getConverter();
+ Renderer<?> renderer = column.getRenderer();
+
+ Item item = cell.getItem();
+ Object modelValue = item.getItemProperty(cell.getPropertyId())
+ .getValue();
+
+ data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer
+ .encodeValue(modelValue, renderer, converter, getLocale()));
+ }
}
/**
@@ -3382,10 +3616,78 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
return JsonCodec.encode(value, null, type,
getUI().getConnectorTracker()).getEncodedValue();
}
+
+ /**
+ * Converts and encodes the given data model property value using the
+ * given converter and renderer. This method is public only for testing
+ * purposes.
+ *
+ * @param renderer
+ * the renderer to use
+ * @param converter
+ * the converter to use
+ * @param modelValue
+ * the value to convert and encode
+ * @param locale
+ * the locale to use in conversion
+ * @return an encoded value ready to be sent to the client
+ */
+ public static <T> JsonValue encodeValue(Object modelValue,
+ Renderer<T> renderer, Converter<?, ?> converter, Locale locale) {
+ Class<T> presentationType = renderer.getPresentationType();
+ T presentationValue;
+
+ if (converter == null) {
+ try {
+ presentationValue = presentationType.cast(modelValue);
+ } catch (ClassCastException e) {
+ if (presentationType == String.class) {
+ // If there is no converter, just fallback to using
+ // toString(). modelValue can't be null as
+ // Class.cast(null) will always succeed
+ presentationValue = (T) modelValue.toString();
+ } else {
+ throw new Converter.ConversionException(
+ "Unable to convert value of type "
+ + modelValue.getClass().getName()
+ + " to presentation type "
+ + presentationType.getName()
+ + ". No converter is set and the types are not compatible.");
+ }
+ }
+ } else {
+ assert presentationType.isAssignableFrom(converter
+ .getPresentationType());
+ @SuppressWarnings("unchecked")
+ Converter<T, Object> safeConverter = (Converter<T, Object>) converter;
+ presentationValue = safeConverter
+ .convertToPresentation(modelValue,
+ safeConverter.getPresentationType(), locale);
+ }
+
+ JsonValue encodedValue;
+ try {
+ encodedValue = renderer.encode(presentationValue);
+ } catch (Exception e) {
+ getLogger().log(Level.SEVERE, "Unable to encode data", e);
+ encodedValue = renderer.encode(null);
+ }
+
+ return encodedValue;
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(AbstractRenderer.class.getName());
+ }
+
}
/**
* An abstract base class for server-side Grid extensions.
+ * <p>
+ * Note: If the extension is an instance of {@link DataGenerator} it will
+ * automatically register itself to {@link RpcDataProviderExtension} of
+ * extended Grid. On remove this registration is automatically removed.
*
* @since 7.5
*/
@@ -3410,6 +3712,26 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
extend(grid);
}
+ @Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+
+ if (this instanceof DataGenerator) {
+ getParentGrid().datasourceExtension
+ .addDataGenerator((DataGenerator) this);
+ }
+ }
+
+ @Override
+ public void remove() {
+ if (this instanceof DataGenerator) {
+ getParentGrid().datasourceExtension
+ .removeDataGenerator((DataGenerator) this);
+ }
+
+ super.remove();
+ }
+
/**
* Gets the item id for a row key.
* <p>
@@ -3451,9 +3773,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
if (getParent() instanceof Grid) {
Grid grid = (Grid) getParent();
return grid;
+ } else if (getParent() == null) {
+ throw new IllegalStateException(
+ "Renderer is not attached to any parent");
} else {
throw new IllegalStateException(
- "Renderers can be used only with Grid");
+ "Renderers can be used only with Grid. Extended "
+ + getParent().getClass().getSimpleName()
+ + " instead");
}
}
}
@@ -3531,6 +3858,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
};
+ private final ItemSetChangeListener editorClosingItemSetListener = new ItemSetChangeListener() {
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ cancelEditor();
+ }
+ };
+
private RpcDataProviderExtension datasourceExtension;
/**
@@ -3556,6 +3890,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
private CellStyleGenerator cellStyleGenerator;
private RowStyleGenerator rowStyleGenerator;
+ private CellDescriptionGenerator cellDescriptionGenerator;
+ private RowDescriptionGenerator rowDescriptionGenerator;
+
/**
* <code>true</code> if Grid is using the internal IndexedContainer created
* in Grid() constructor, or <code>false</code> if the user has set their
@@ -3873,34 +4210,61 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
userOriginated);
}
}
+
+ @Override
+ public void editorOpen(String rowKey) {
+ fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper()
+ .getItemId(rowKey)));
+ }
+
+ @Override
+ public void editorMove(String rowKey) {
+ fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper()
+ .getItemId(rowKey)));
+ }
+
+ @Override
+ public void editorClose(String rowKey) {
+ fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper()
+ .getItemId(rowKey)));
+ }
});
registerRpc(new EditorServerRpc() {
@Override
public void bind(int rowIndex) {
- Exception exception = null;
try {
Object id = getContainerDataSource().getIdByIndex(rowIndex);
- if (editedItemId == null) {
- editedItemId = id;
- }
- if (editedItemId.equals(id)) {
- doEditItem();
+ final boolean opening = editedItemId == null;
+
+ final boolean moving = !opening && !editedItemId.equals(id);
+
+ final boolean allowMove = !isEditorBuffered()
+ && getEditorFieldGroup().isValid();
+
+ if (opening || !moving || allowMove) {
+ doBind(id);
+ } else {
+ failBind(null);
}
} catch (Exception e) {
- exception = e;
+ failBind(e);
}
+ }
- if (exception != null) {
- handleError(exception);
- doCancelEditor();
- getEditorRpc().confirmBind(false);
- } else {
- doEditItem();
- getEditorRpc().confirmBind(true);
+ private void doBind(Object id) {
+ editedItemId = id;
+ doEditItem();
+ getEditorRpc().confirmBind(true);
+ }
+
+ private void failBind(Exception e) {
+ if (e != null) {
+ handleError(e);
}
+ getEditorRpc().confirmBind(false);
}
@Override
@@ -4009,10 +4373,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
removeExtension(datasourceExtension);
}
- datasource = container;
-
resetEditor();
+ datasource = container;
+
//
// Adjust sort order
//
@@ -4041,7 +4405,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
datasourceExtension = new RpcDataProviderExtension(container);
- datasourceExtension.extend(this, columnKeys);
+ datasourceExtension.extend(this);
+ datasourceExtension.addDataGenerator(new RowDataGenerator());
detailComponentManager = datasourceExtension
.getDetailComponentManager();
@@ -4059,6 +4424,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
((PropertySetChangeNotifier) datasource)
.addPropertySetChangeListener(propertyListener);
}
+
/*
* activeRowHandler will be updated by the client-side request that
* occurs on container change - no need to actively re-insert any
@@ -5499,6 +5865,73 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
/**
+ * Sets the {@code CellDescriptionGenerator} instance for generating
+ * optional descriptions (tooltips) for individual Grid cells. If a
+ * {@link RowDescriptionGenerator} is also set, the row description it
+ * generates is displayed for cells for which {@code generator} returns
+ * null.
+ *
+ * @param generator
+ * the description generator to use or {@code null} to remove a
+ * previously set generator if any
+ *
+ * @see #setRowDescriptionGenerator(RowDescriptionGenerator)
+ *
+ * @since
+ */
+ public void setCellDescriptionGenerator(CellDescriptionGenerator generator) {
+ cellDescriptionGenerator = generator;
+ getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null);
+ datasourceExtension.refreshCache();
+ }
+
+ /**
+ * Returns the {@code CellDescriptionGenerator} instance used to generate
+ * descriptions (tooltips) for Grid cells.
+ *
+ * @return the description generator or {@code null} if no generator is set
+ *
+ * @since
+ */
+ public CellDescriptionGenerator getCellDescriptionGenerator() {
+ return cellDescriptionGenerator;
+ }
+
+ /**
+ * Sets the {@code RowDescriptionGenerator} instance for generating optional
+ * descriptions (tooltips) for Grid rows. If a
+ * {@link CellDescriptionGenerator} is also set, the row description
+ * generated by {@code generator} is used for cells for which the cell
+ * description generator returns null.
+ *
+ *
+ * @param generator
+ * the description generator to use or {@code null} to remove a
+ * previously set generator if any
+ *
+ * @see #setCellDescriptionGenerator(CellDescriptionGenerator)
+ *
+ * @since
+ */
+ public void setRowDescriptionGenerator(RowDescriptionGenerator generator) {
+ rowDescriptionGenerator = generator;
+ getState().hasDescriptions = (generator != null || cellDescriptionGenerator != null);
+ datasourceExtension.refreshCache();
+ }
+
+ /**
+ * Returns the {@code RowDescriptionGenerator} instance used to generate
+ * descriptions (tooltips) for Grid rows
+ *
+ * @return the description generator or {@code} null if no generator is set
+ *
+ * @since
+ */
+ public RowDescriptionGenerator getRowDescriptionGenerator() {
+ return rowDescriptionGenerator;
+ }
+
+ /**
* Sets the style generator that is used for generating styles for cells
*
* @param cellStyleGenerator
@@ -5507,8 +5940,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
*/
public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
this.cellStyleGenerator = cellStyleGenerator;
- getState().hasCellStyleGenerator = (cellStyleGenerator != null);
-
datasourceExtension.refreshCache();
}
@@ -5531,8 +5962,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
*/
public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) {
this.rowStyleGenerator = rowStyleGenerator;
- getState().hasRowStyleGenerator = (rowStyleGenerator != null);
-
datasourceExtension.refreshCache();
}
@@ -5742,10 +6171,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
* Opens the editor interface for the provided item. Scrolls the Grid to
* bring the item to view if it is not already visible.
*
+ * Note that any cell content rendered by a WidgetRenderer will not be
+ * visible in the editor row.
+ *
* @param itemId
* the id of the item to edit
* @throws IllegalStateException
- * if the editor is not enabled or already editing an item
+ * if the editor is not enabled or already editing an item in
+ * buffered mode
* @throws IllegalArgumentException
* if the {@code itemId} is not in the backing container
* @see #setEditorEnabled(boolean)
@@ -5754,8 +6187,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
IllegalArgumentException {
if (!isEditorEnabled()) {
throw new IllegalStateException("Item editor is not enabled");
- } else if (editedItemId != null) {
- throw new IllegalStateException("Editing item + " + itemId
+ } else if (isEditorBuffered() && editedItemId != null) {
+ throw new IllegalStateException("Editing item " + itemId
+ " failed. Item editor is already editing item "
+ editedItemId);
} else if (!getContainerDataSource().containsId(itemId)) {
@@ -5783,6 +6216,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
f.markAsDirtyRecursive();
}
+ if (datasource instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) datasource)
+ .addItemSetChangeListener(editorClosingItemSetListener);
+ }
}
private void setEditorField(Object propertyId, Field<?> field) {
@@ -5832,6 +6269,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
editorFieldGroup.discard();
editorFieldGroup.setItemDataSource(null);
+ if (datasource instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) datasource)
+ .removeItemSetChangeListener(editorClosingItemSetListener);
+ }
+
// Mark Grid as dirty so the client side gets to know that the editors
// are no longer attached
markAsDirty();
@@ -5981,6 +6423,70 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
return getState(false).editorCancelCaption;
}
+ /**
+ * Add an editor event listener
+ *
+ * @param listener
+ * the event listener object to add
+ */
+ public void addEditorListener(EditorListener listener) {
+ addListener(GridConstants.EDITOR_OPEN_EVENT_ID, EditorOpenEvent.class,
+ listener, EditorListener.EDITOR_OPEN_METHOD);
+ addListener(GridConstants.EDITOR_MOVE_EVENT_ID, EditorMoveEvent.class,
+ listener, EditorListener.EDITOR_MOVE_METHOD);
+ addListener(GridConstants.EDITOR_CLOSE_EVENT_ID,
+ EditorCloseEvent.class, listener,
+ EditorListener.EDITOR_CLOSE_METHOD);
+ }
+
+ /**
+ * Remove an editor event listener
+ *
+ * @param listener
+ * the event listener object to remove
+ */
+ public void removeEditorListener(EditorListener listener) {
+ removeListener(GridConstants.EDITOR_OPEN_EVENT_ID,
+ EditorOpenEvent.class, listener);
+ removeListener(GridConstants.EDITOR_MOVE_EVENT_ID,
+ EditorMoveEvent.class, listener);
+ removeListener(GridConstants.EDITOR_CLOSE_EVENT_ID,
+ EditorCloseEvent.class, listener);
+ }
+
+ /**
+ * Sets the buffered editor mode. The default mode is buffered (
+ * <code>true</code>).
+ *
+ * @since 7.6
+ * @param editorBuffered
+ * <code>true</code> to enable buffered editor,
+ * <code>false</code> to disable it
+ * @throws IllegalStateException
+ * If editor is active while attempting to change the buffered
+ * mode.
+ */
+ public void setEditorBuffered(boolean editorBuffered)
+ throws IllegalStateException {
+ if (isEditorActive()) {
+ throw new IllegalStateException(
+ "Can't change editor unbuffered mode while editor is active.");
+ }
+ getState().editorBuffered = editorBuffered;
+ editorFieldGroup.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 getState().editorBuffered;
+ }
+
@Override
public void addItemClickListener(ItemClickListener listener) {
addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
@@ -6273,6 +6779,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
result.add("footer-visible");
result.add("editor-error-handler");
result.add("height-mode");
+
return result;
}
}
diff --git a/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java b/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java
index eb07fae07f..cea8df0ba6 100644
--- a/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java
+++ b/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java
@@ -15,8 +15,17 @@
*/
package com.vaadin.tests.server.renderer;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Date;
+import java.util.Locale;
+
+import org.junit.Before;
+import org.junit.Test;
+
import com.vaadin.data.Item;
-import com.vaadin.data.RpcDataProviderExtension;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.StringToIntegerConverter;
@@ -24,22 +33,15 @@ import com.vaadin.server.VaadinSession;
import com.vaadin.tests.server.component.grid.TestGrid;
import com.vaadin.tests.util.AlwaysLockedVaadinSession;
import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.AbstractRenderer;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.renderers.ButtonRenderer;
import com.vaadin.ui.renderers.DateRenderer;
import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.NumberRenderer;
import com.vaadin.ui.renderers.TextRenderer;
-import elemental.json.JsonValue;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Date;
-import java.util.Locale;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
+import elemental.json.JsonValue;
public class RendererTest {
@@ -259,7 +261,7 @@ public class RendererTest {
}
private JsonValue render(Column column, Object value) {
- return RpcDataProviderExtension.encodeValue(value,
- column.getRenderer(), column.getConverter(), grid.getLocale());
+ return AbstractRenderer.encodeValue(value, column.getRenderer(),
+ column.getConverter(), grid.getLocale());
}
}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridConstants.java b/shared/src/com/vaadin/shared/ui/grid/GridConstants.java
index 0606e4b1cc..5b2ac96975 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridConstants.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridConstants.java
@@ -74,4 +74,19 @@ public final class GridConstants implements Serializable {
/** The default cancel button caption in the editor */
public static final String DEFAULT_CANCEL_CAPTION = "Cancel";
+
+ /**
+ * Event ID constant for editor open event
+ */
+ public static final String EDITOR_OPEN_EVENT_ID = "editorOpen";
+
+ /**
+ * Event ID constant for editor move event
+ */
+ public static final String EDITOR_MOVE_EVENT_ID = "editorMove";
+
+ /**
+ * Event ID constant for editor close event
+ */
+ public static final String EDITOR_CLOSE_EVENT_ID = "editorClose";
}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
index 5c91f2b746..a178ff63d0 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
@@ -37,6 +37,31 @@ public interface GridServerRpc extends ServerRpc {
boolean userOriginated);
/**
+ * Informs the server that the editor was opened (fresh) on a certain row
+ *
+ * @param rowKey
+ * a key identifying item the editor was opened on
+ */
+ void editorOpen(String rowKey);
+
+ /**
+ * Informs the server that the editor was reopened (without closing it in
+ * between) on another row
+ *
+ * @param rowKey
+ * a key identifying item the editor was opened on
+ */
+ void editorMove(String rowKey);
+
+ /**
+ * Informs the server that the editor was closed
+ *
+ * @param rowKey
+ * a key identifying item the editor was opened on
+ */
+ void editorClose(String rowKey);
+
+ /**
* Informs the server that an item has been clicked in Grid.
*
* @param rowKey
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java
index c23c931683..0d0a5d3e9f 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridState.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java
@@ -19,9 +19,9 @@ package com.vaadin.shared.ui.grid;
import java.util.ArrayList;
import java.util.List;
-import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.annotations.DelegateToWidget;
import com.vaadin.shared.data.sort.SortDirection;
+import com.vaadin.shared.ui.TabIndexState;
/**
* The shared state for the {@link com.vaadin.ui.components.grid.Grid} component
@@ -29,7 +29,7 @@ import com.vaadin.shared.data.sort.SortDirection;
* @since 7.4
* @author Vaadin Ltd
*/
-public class GridState extends AbstractComponentState {
+public class GridState extends TabIndexState {
/**
* A description of which of the three bundled SelectionModels is currently
@@ -103,6 +103,20 @@ public class GridState extends AbstractComponentState {
public static final String JSONKEY_CELLSTYLES = "cs";
/**
+ * The key in which a row's description can be found
+ *
+ * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, String)
+ */
+ public static final String JSONKEY_ROWDESCRIPTION = "rd";
+
+ /**
+ * The key in which a cell's description can be found
+ *
+ * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, String)
+ */
+ public static final String JSONKEY_CELLDESCRIPTION = "cd";
+
+ /**
* The key that tells whether details are visible for the row.
*
* @since 7.5.0
@@ -156,10 +170,12 @@ public class GridState extends AbstractComponentState {
/** The enabled state of the editor interface */
public boolean editorEnabled = false;
- /** Whether row data might contain generated row styles */
- public boolean hasRowStyleGenerator;
- /** Whether row data might contain generated cell styles */
- public boolean hasCellStyleGenerator;
+ /** Buffered editor mode */
+ @DelegateToWidget
+ public boolean editorBuffered = true;
+
+ /** Whether rows and/or cells have generated descriptions (tooltips) */
+ public boolean hasDescriptions;
/** The caption for the save button in the editor */
@DelegateToWidget
diff --git a/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java b/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java
index b289279b86..aca617aa5a 100644
--- a/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java
+++ b/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java
@@ -20,6 +20,7 @@ import com.vaadin.server.ThemeResource;
import com.vaadin.tests.util.Log;
import com.vaadin.tests.util.LoremIpsum;
import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.MenuBar;
import com.vaadin.ui.MenuBar.MenuItem;
import com.vaadin.ui.themes.BaseTheme;
@@ -242,16 +243,18 @@ public abstract class AbstractComponentTest<T extends AbstractComponent>
createStyleNameSelect(CATEGORY_DECORATIONS);
+ createFocusActions();
}
protected Command<T, Boolean> focusListenerCommand = new Command<T, Boolean>() {
@Override
public void execute(T c, Boolean value, Object data) {
+ FocusNotifier fn = (FocusNotifier) c;
if (value) {
- ((FocusNotifier) c).addListener(AbstractComponentTest.this);
+ fn.addFocusListener(AbstractComponentTest.this);
} else {
- ((FocusNotifier) c).removeListener(AbstractComponentTest.this);
+ fn.removeFocusListener(AbstractComponentTest.this);
}
}
};
@@ -259,10 +262,11 @@ public abstract class AbstractComponentTest<T extends AbstractComponent>
@Override
public void execute(T c, Boolean value, Object data) {
+ BlurNotifier bn = (BlurNotifier) c;
if (value) {
- ((BlurNotifier) c).addListener(AbstractComponentTest.this);
+ bn.addBlurListener(AbstractComponentTest.this);
} else {
- ((BlurNotifier) c).removeListener(AbstractComponentTest.this);
+ bn.removeBlurListener(AbstractComponentTest.this);
}
}
};
@@ -279,6 +283,35 @@ public abstract class AbstractComponentTest<T extends AbstractComponent>
}
+ private void createFocusActions() {
+ if (FocusNotifier.class.isAssignableFrom(getTestClass())) {
+ createFocusListener(CATEGORY_LISTENERS);
+ }
+ if (BlurNotifier.class.isAssignableFrom(getTestClass())) {
+ createBlurListener(CATEGORY_LISTENERS);
+ }
+ if (Focusable.class.isAssignableFrom(getTestClass())) {
+ LinkedHashMap<String, Integer> tabIndexes = new LinkedHashMap<String, Integer>();
+ tabIndexes.put("0", 0);
+ tabIndexes.put("-1", -1);
+ tabIndexes.put("10", 10);
+ createSelectAction("Tab index", "State", tabIndexes, "0",
+ new Command<T, Integer>() {
+ @Override
+ public void execute(T c, Integer tabIndex, Object data) {
+ ((Focusable) c).setTabIndex(tabIndex);
+ }
+ });
+
+ createClickAction("Set focus", "State", new Command<T, Void>() {
+ @Override
+ public void execute(T c, Void value, Object data) {
+ ((Focusable) c).focus();
+ }
+ }, null);
+ }
+ }
+
private void createStyleNameSelect(String category) {
LinkedHashMap<String, String> options = new LinkedHashMap<String, String>();
options.put("-", null);
diff --git a/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java b/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java
index 692ca25b07..496a44a6c1 100644
--- a/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java
+++ b/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java
@@ -13,8 +13,6 @@ import com.vaadin.data.Property;
import com.vaadin.data.Property.ReadOnlyStatusChangeEvent;
import com.vaadin.data.Property.ReadOnlyStatusChangeListener;
import com.vaadin.data.Property.ValueChangeListener;
-import com.vaadin.event.FieldEvents.BlurNotifier;
-import com.vaadin.event.FieldEvents.FocusNotifier;
import com.vaadin.tests.components.AbstractComponentTest;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.MenuBar;
@@ -29,15 +27,9 @@ public abstract class AbstractFieldTest<T extends AbstractField> extends
@Override
protected void createActions() {
super.createActions();
+
createBooleanAction("Required", CATEGORY_STATE, false, requiredCommand);
createRequiredErrorSelect(CATEGORY_DECORATIONS);
- if (FocusNotifier.class.isAssignableFrom(getTestClass())) {
- createFocusListener(CATEGORY_LISTENERS);
- }
-
- if (BlurNotifier.class.isAssignableFrom(getTestClass())) {
- createBlurListener(CATEGORY_LISTENERS);
- }
createValueChangeListener(CATEGORY_LISTENERS);
createReadOnlyStatusChangeListener(CATEGORY_LISTENERS);
@@ -52,7 +44,6 @@ public abstract class AbstractFieldTest<T extends AbstractField> extends
// * invalidallowed
// * error indicator
//
- // * tabindex
// * validation visible
// * ShortcutListener
diff --git a/uitest/src/com/vaadin/tests/components/button/Buttons2.java b/uitest/src/com/vaadin/tests/components/button/Buttons2.java
index 7526e7dbc3..4f75dfbfef 100644
--- a/uitest/src/com/vaadin/tests/components/button/Buttons2.java
+++ b/uitest/src/com/vaadin/tests/components/button/Buttons2.java
@@ -42,9 +42,6 @@ public class Buttons2<T extends Button> extends AbstractComponentTest<T>
protected void createActions() {
super.createActions();
- createFocusListener(CATEGORY_LISTENERS);
- createBlurListener(CATEGORY_LISTENERS);
-
createBooleanAction("Disable on click", CATEGORY_FEATURES, false,
disableOnClickCommand);
addClickListener(CATEGORY_LISTENERS);
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
index 6511866897..23226fb6cf 100644
--- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
@@ -29,6 +29,8 @@ import java.util.Random;
import com.vaadin.data.Container.Filter;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
import com.vaadin.data.sort.Sort;
import com.vaadin.data.sort.SortOrder;
@@ -48,7 +50,9 @@ import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.Field;
import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.CellDescriptionGenerator;
import com.vaadin.ui.Grid.CellReference;
import com.vaadin.ui.Grid.CellStyleGenerator;
import com.vaadin.ui.Grid.Column;
@@ -57,10 +61,15 @@ import com.vaadin.ui.Grid.ColumnReorderListener;
import com.vaadin.ui.Grid.ColumnVisibilityChangeEvent;
import com.vaadin.ui.Grid.ColumnVisibilityChangeListener;
import com.vaadin.ui.Grid.DetailsGenerator;
+import com.vaadin.ui.Grid.EditorCloseEvent;
+import com.vaadin.ui.Grid.EditorListener;
+import com.vaadin.ui.Grid.EditorMoveEvent;
+import com.vaadin.ui.Grid.EditorOpenEvent;
import com.vaadin.ui.Grid.FooterCell;
import com.vaadin.ui.Grid.HeaderCell;
import com.vaadin.ui.Grid.HeaderRow;
import com.vaadin.ui.Grid.MultiSelectionModel;
+import com.vaadin.ui.Grid.RowDescriptionGenerator;
import com.vaadin.ui.Grid.RowReference;
import com.vaadin.ui.Grid.RowStyleGenerator;
import com.vaadin.ui.Grid.SelectionMode;
@@ -123,6 +132,45 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
};
+ private RowDescriptionGenerator rowDescriptionGenerator = new RowDescriptionGenerator() {
+
+ @Override
+ public String getDescription(RowReference row) {
+ return "Row tooltip for row " + row.getItemId();
+ }
+ };
+
+ private CellDescriptionGenerator cellDescriptionGenerator = new CellDescriptionGenerator() {
+
+ @Override
+ public String getDescription(CellReference cell) {
+ if ("Column 0".equals(cell.getPropertyId())) {
+ return "Cell tooltip for row " + cell.getItemId()
+ + ", column 0";
+ } else {
+ return null;
+ }
+ }
+ };
+
+ private ItemClickListener editorOpeningItemClickListener = new ItemClickListener() {
+
+ @Override
+ public void itemClick(ItemClickEvent event) {
+ grid.editItem(event.getItemId());
+ }
+ };
+
+ private ValueChangeListener reactiveValueChanger = new ValueChangeListener() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void valueChange(ValueChangeEvent event) {
+ Object id = grid.getEditedItemId();
+ grid.getContainerDataSource().getContainerProperty(id, "Column 2")
+ .setValue("Modified");
+ }
+ };
+
private ColumnReorderListener columnReorderListener = new ColumnReorderListener() {
@Override
@@ -273,6 +321,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
new NumberRenderer(new DecimalFormat("0,000.00",
DecimalFormatSymbols.getInstance(new Locale("fi",
"FI")))));
+
grid.getColumn(getColumnProperty(col++)).setRenderer(
new DateRenderer(new SimpleDateFormat("dd.MM.yy HH:mm")));
grid.getColumn(getColumnProperty(col++)).setRenderer(
@@ -362,49 +411,58 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
private void addFilterActions() {
- createClickAction("Column 1 starts with \"(23\"", "Filter",
- new Command<Grid, Void>() {
- @Override
- public void execute(Grid grid, Void value, Object data) {
- ds.addContainerFilter(new Filter() {
+ createBooleanAction("Column 1 starts with \"(23\"", "Filter", false,
+ new Command<Grid, Boolean>() {
+ Filter filter = new Filter() {
+ @Override
+ public boolean passesFilter(Object itemId, Item item) {
+ return item.getItemProperty("Column 1").getValue()
+ .toString().startsWith("(23");
+ }
- @Override
- public boolean passesFilter(Object itemId, Item item)
- throws UnsupportedOperationException {
- return item.getItemProperty("Column 1")
- .getValue().toString()
- .startsWith("(23");
- }
+ @Override
+ public boolean appliesToProperty(Object propertyId) {
+ return propertyId.equals("Column 1");
+ }
+ };
- @Override
- public boolean appliesToProperty(Object propertyId) {
- return propertyId.equals("Column 1");
- }
- });
+ @Override
+ public void execute(Grid grid, Boolean value, Object data) {
+ if (value) {
+ ds.addContainerFilter(filter);
+ } else {
+ ds.removeContainerFilter(filter);
+ }
}
- }, null);
+ });
- createClickAction("Add impassable filter", "Filter",
- new Command<Grid, Void>() {
- @Override
- public void execute(Grid c, Void value, Object data) {
- ds.addContainerFilter(new Filter() {
- @Override
- public boolean passesFilter(Object itemId, Item item)
- throws UnsupportedOperationException {
- return false;
- }
+ createBooleanAction("Impassable filter", "Filter", false,
+ new Command<Grid, Boolean>() {
+ Filter filter = new Filter() {
+ @Override
+ public boolean passesFilter(Object itemId, Item item) {
+ return false;
+ }
- @Override
- public boolean appliesToProperty(Object propertyId) {
- return true;
- }
- });
+ @Override
+ public boolean appliesToProperty(Object propertyId) {
+ return true;
+ }
+ };
+
+ @Override
+ public void execute(Grid c, Boolean value, Object data) {
+ if (value) {
+ ds.addContainerFilter(filter);
+ } else {
+ ds.removeContainerFilter(filter);
+ }
}
- }, null);
+ });
}
protected void createGridActions() {
+
LinkedHashMap<String, String> primaryStyleNames = new LinkedHashMap<String, String>();
primaryStyleNames.put("v-grid", "v-grid");
primaryStyleNames.put("v-escalator", "v-escalator");
@@ -594,6 +652,25 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
});
+ createBooleanAction("Row description generator", "State", false,
+ new Command<Grid, Boolean>() {
+
+ @Override
+ public void execute(Grid c, Boolean value, Object data) {
+ c.setRowDescriptionGenerator(value ? rowDescriptionGenerator
+ : null);
+ }
+ });
+
+ createBooleanAction("Cell description generator", "State", false,
+ new Command<Grid, Boolean>() {
+ @Override
+ public void execute(Grid c, Boolean value, Object data) {
+ c.setCellDescriptionGenerator(value ? cellDescriptionGenerator
+ : null);
+ }
+ });
+
LinkedHashMap<String, Integer> frozenOptions = new LinkedHashMap<String, Integer>();
for (int i = -1; i <= COLUMNS; i++) {
frozenOptions.put(String.valueOf(i), Integer.valueOf(i));
@@ -638,6 +715,39 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
});
+
+ createBooleanAction("EditorOpeningItemClickListener", "State", false,
+ new Command<Grid, Boolean>() {
+
+ @Override
+ public void execute(Grid c, Boolean value, Object data) {
+ if (!value) {
+ c.removeItemClickListener(editorOpeningItemClickListener);
+ } else {
+ c.addItemClickListener(editorOpeningItemClickListener);
+ }
+ }
+
+ });
+ createBooleanAction("ReactiveValueChanger", "State", false,
+ new Command<Grid, Boolean>() {
+
+ @Override
+ public void execute(Grid c, Boolean value, Object data) {
+ Field<?> targetField = grid.getEditorFieldGroup()
+ .getField("Column 0");
+ if (targetField != null) {
+ if (!value) {
+ targetField
+ .removeValueChangeListener(reactiveValueChanger);
+ } else {
+ targetField
+ .addValueChangeListener(reactiveValueChanger);
+ }
+ }
+ }
+
+ });
createBooleanAction("ColumnReorderListener", "State", false,
new Command<Grid, Boolean>() {
@@ -661,7 +771,6 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
}
});
-
createBooleanAction("Single select allow deselect", "State",
singleSelectAllowDeselect, new Command<Grid, Boolean>() {
@Override
@@ -1238,6 +1347,14 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
});
+ createBooleanAction("Buffered mode", "Editor", true,
+ new Command<Grid, Boolean>() {
+ @Override
+ public void execute(Grid c, Boolean value, Object data) {
+ c.setEditorBuffered(value);
+ }
+ });
+
createClickAction("Edit item 5", "Editor", new Command<Grid, String>() {
@Override
public void execute(Grid c, String value, Object data) {
@@ -1285,6 +1402,30 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
c.setEditorCancelCaption("ʃǝɔuɐↃ");
}
}, null);
+
+ createClickAction("Add editor state listener", "Editor",
+ new Command<Grid, String>() {
+ @Override
+ public void execute(Grid grid, String value, Object data) {
+ grid.addEditorListener(new EditorListener() {
+ @Override
+ public void editorOpened(EditorOpenEvent e) {
+ log("Editor opened");
+ }
+
+ @Override
+ public void editorMoved(EditorMoveEvent e) {
+ log("Editor moved");
+ }
+
+ @Override
+ public void editorClosed(EditorCloseEvent e) {
+ log("Editor closed");
+ }
+ });
+ }
+ }, null);
+
}
@SuppressWarnings("boxing")
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java
new file mode 100644
index 0000000000..ed712361a6
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.tests.components.grid.basicfeatures;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.openqa.selenium.By;
+
+public class GridDescriptionGeneratorTest extends GridBasicFeaturesTest {
+
+ @Test
+ public void testCellDescription() {
+ openTestURL();
+ selectMenuPath("Component", "State", "Cell description generator");
+
+ getGridElement().getCell(1, 0).showTooltip();
+ String tooltipText = findElement(By.className("v-tooltip-text"))
+ .getText();
+ assertEquals("Tooltip text", "Cell tooltip for row 1, column 0",
+ tooltipText);
+
+ getGridElement().getCell(1, 1).showTooltip();
+ assertTrue("Tooltip should not be present in cell (1, 1) ",
+ findElement(By.className("v-tooltip-text")).getText().isEmpty());
+ }
+
+ @Test
+ public void testRowDescription() {
+ openTestURL();
+ selectMenuPath("Component", "State", "Row description generator");
+
+ getGridElement().getCell(5, 3).showTooltip();
+ String tooltipText = findElement(By.className("v-tooltip-text"))
+ .getText();
+ assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText);
+
+ getGridElement().getCell(15, 3).showTooltip();
+ tooltipText = findElement(By.className("v-tooltip-text")).getText();
+ assertEquals("Tooltip text", "Row tooltip for row 15", tooltipText);
+ }
+
+ @Test
+ public void testRowAndCellDescription() {
+ openTestURL();
+ selectMenuPath("Component", "State", "Row description generator");
+ selectMenuPath("Component", "State", "Cell description generator");
+
+ getGridElement().getCell(5, 0).showTooltip();
+ String tooltipText = findElement(By.className("v-tooltip-text"))
+ .getText();
+ assertEquals("Tooltip text", "Cell tooltip for row 5, column 0",
+ tooltipText);
+
+ getGridElement().getCell(5, 3).showTooltip();
+ tooltipText = findElement(By.className("v-tooltip-text")).getText();
+ assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText);
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java
index 43fe29bc02..58d6559bfb 100644
--- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java
@@ -143,7 +143,7 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest {
List<WebElement> selectorDivs = editorCells.findElements(By
.cssSelector("div"));
- assertTrue("selector column cell should've been empty", selectorDivs
+ assertFalse("selector column cell should've had contents", selectorDivs
.get(0).getAttribute("innerHTML").isEmpty());
assertFalse("normal column cell shoul've had contents", selectorDivs
.get(1).getAttribute("innerHTML").isEmpty());
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java
new file mode 100644
index 0000000000..57f4b877df
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.tests.components.grid.basicfeatures.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.shared.ui.grid.GridConstants;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.GridElement.GridEditorElement;
+import com.vaadin.testbench.elements.NotificationElement;
+
+public class GridEditorBufferedTest extends GridEditorTest {
+
+ @Override
+ @Before
+ public void setUp() {
+ super.setUp();
+ }
+
+ @Test
+ public void testSave() {
+ selectMenuPath(EDIT_ITEM_100);
+
+ WebElement textField = getEditorWidgets().get(0);
+
+ textField.click();
+
+ textField.sendKeys(" changed");
+
+ WebElement saveButton = getEditor().findElement(
+ By.className("v-grid-editor-save"));
+
+ saveButton.click();
+
+ assertEquals("(100, 0) changed", getGridElement().getCell(100, 0)
+ .getText());
+ }
+
+ @Test
+ public void testProgrammaticSave() {
+ selectMenuPath(EDIT_ITEM_100);
+
+ WebElement textField = getEditorWidgets().get(0);
+
+ textField.click();
+
+ textField.sendKeys(" changed");
+
+ selectMenuPath("Component", "Editor", "Save");
+
+ assertEquals("(100, 0) changed", getGridElement().getCell(100, 0)
+ .getText());
+ }
+
+ @Test
+ public void testInvalidEdition() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertFalse(logContainsText("Exception occured, java.lang.IllegalStateException"));
+
+ GridEditorElement editor = getGridElement().getEditor();
+
+ assertFalse(
+ "Field 7 should not have been marked with an error before error",
+ editor.isFieldErrorMarked(7));
+
+ WebElement intField = editor.getField(7);
+ intField.clear();
+ intField.sendKeys("banana phone");
+ editor.save();
+
+ assertEquals("Column 7: Could not convert value to Integer",
+ editor.getErrorMessage());
+ assertTrue("Field 7 should have been marked with an error after error",
+ editor.isFieldErrorMarked(7));
+ editor.cancel();
+
+ selectMenuPath(EDIT_ITEM_100);
+ assertFalse("Exception should not exist",
+ isElementPresent(NotificationElement.class));
+ assertEquals("There should be no editor error message", null,
+ getGridElement().getEditor().getErrorMessage());
+ }
+
+ @Test
+ public void testEditorInDisabledGrid() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
+
+ selectMenuPath("Component", "State", "Enabled");
+ assertEditorOpen();
+
+ GridEditorElement editor = getGridElement().getEditor();
+ editor.save();
+ assertEditorOpen();
+
+ editor.cancel();
+ assertEditorOpen();
+
+ selectMenuPath("Component", "State", "Enabled");
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testCaptionChange() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertEquals("Save button caption should've been \""
+ + GridConstants.DEFAULT_SAVE_CAPTION + "\" to begin with",
+ GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText());
+ assertEquals("Cancel button caption should've been \""
+ + GridConstants.DEFAULT_CANCEL_CAPTION + "\" to begin with",
+ GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton()
+ .getText());
+
+ selectMenuPath("Component", "Editor", "Change save caption");
+ assertNotEquals(
+ "Save button caption should've changed while editor is open",
+ GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText());
+
+ getCancelButton().click();
+
+ selectMenuPath("Component", "Editor", "Change cancel caption");
+ selectMenuPath(EDIT_ITEM_5);
+ assertNotEquals(
+ "Cancel button caption should've changed while editor is closed",
+ GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton()
+ .getText());
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testVerticalScrollLocking() {
+ selectMenuPath(EDIT_ITEM_5);
+ getGridElement().getCell(200, 0);
+ }
+
+ @Test
+ public void testScrollDisabledOnProgrammaticOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ selectMenuPath(EDIT_ITEM_5);
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testScrollDisabledOnMouseOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ new Actions(getDriver()).doubleClick(cell_5_0).perform();
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testScrollDisabledOnKeyboardOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ cell_5_0.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testMouseOpeningClosing() {
+
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorOpen();
+
+ getCancelButton().click();
+ assertEditorClosed();
+
+ selectMenuPath(TOGGLE_EDIT_ENABLED);
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorClosed();
+ }
+
+ @Test
+ public void testMouseOpeningDisabledWhenOpen() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ getGridElement().getCell(4, 0).doubleClick();
+
+ assertEquals("Editor should still edit row 5", "(5, 0)",
+ getEditorWidgets().get(0).getAttribute("value"));
+ }
+
+ @Test
+ public void testKeyboardOpeningDisabledWhenOpen() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ new Actions(getDriver()).click(getGridElement().getCell(4, 0))
+ .sendKeys(Keys.ENTER).perform();
+
+ assertEquals("Editor should still edit row 5", "(5, 0)",
+ getEditorWidgets().get(0).getAttribute("value"));
+ }
+
+ @Test
+ public void testProgrammaticOpeningDisabledWhenOpen() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
+ assertEquals("Editor should edit row 5", "(5, 0)", getEditorWidgets()
+ .get(0).getAttribute("value"));
+
+ selectMenuPath(EDIT_ITEM_100);
+ boolean thrown = logContainsText("Exception occured, java.lang.IllegalStateException");
+ assertTrue("IllegalStateException thrown", thrown);
+
+ assertEditorOpen();
+ assertEquals("Editor should still edit row 5", "(5, 0)",
+ getEditorWidgets().get(0).getAttribute("value"));
+ }
+
+ @Test
+ public void testUserSortDisabledWhenOpen() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ getGridElement().getHeaderCell(0, 0).click();
+
+ assertEditorOpen();
+ assertEquals("(2, 0)", getGridElement().getCell(2, 0).getText());
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java
index e772ed44e9..0cba2ce34b 100644
--- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java
@@ -17,7 +17,6 @@ package com.vaadin.tests.components.grid.basicfeatures.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -28,24 +27,26 @@ import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
-import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
-import com.vaadin.shared.ui.grid.GridConstants;
+import com.vaadin.testbench.TestBenchElement;
import com.vaadin.testbench.elements.GridElement.GridCellElement;
import com.vaadin.testbench.elements.GridElement.GridEditorElement;
-import com.vaadin.testbench.elements.NotificationElement;
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeatures;
import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest;
-public class GridEditorTest extends GridBasicFeaturesTest {
+public abstract class GridEditorTest extends GridBasicFeaturesTest {
- private static final String[] EDIT_ITEM_5 = new String[] { "Component",
+ protected static final By BY_EDITOR_CANCEL = By
+ .className("v-grid-editor-cancel");
+ protected static final By BY_EDITOR_SAVE = By
+ .className("v-grid-editor-save");
+ protected static final String[] EDIT_ITEM_5 = new String[] { "Component",
"Editor", "Edit item 5" };
- private static final String[] EDIT_ITEM_100 = new String[] { "Component",
+ protected static final String[] EDIT_ITEM_100 = new String[] { "Component",
"Editor", "Edit item 100" };
- private static final String[] TOGGLE_EDIT_ENABLED = new String[] {
+ protected static final String[] TOGGLE_EDIT_ENABLED = new String[] {
"Component", "Editor", "Enabled" };
@Before
@@ -88,26 +89,6 @@ public class GridEditorTest extends GridBasicFeaturesTest {
assertEditorOpen();
}
- @Test(expected = NoSuchElementException.class)
- public void testVerticalScrollLocking() {
- selectMenuPath(EDIT_ITEM_5);
- getGridElement().getCell(200, 0);
- }
-
- @Test
- public void testMouseOpeningClosing() {
-
- getGridElement().getCell(4, 0).doubleClick();
- assertEditorOpen();
-
- getCancelButton().click();
- assertEditorClosed();
-
- selectMenuPath(TOGGLE_EDIT_ENABLED);
- getGridElement().getCell(4, 0).doubleClick();
- assertEditorClosed();
- }
-
@Test
public void testKeyboardOpeningClosing() {
@@ -141,238 +122,180 @@ public class GridEditorTest extends GridBasicFeaturesTest {
assertEquals("<b>100</b>", widgets.get(8).getAttribute("value"));
}
- @Test
- public void testSave() {
- selectMenuPath(EDIT_ITEM_100);
-
- WebElement textField = getEditorWidgets().get(0);
-
- textField.click();
-
- textField.sendKeys(" changed");
+ protected void assertEditorOpen() {
+ assertEquals("Unexpected number of widgets",
+ GridBasicFeatures.EDITABLE_COLUMNS, getEditorWidgets().size());
+ }
- WebElement saveButton = getEditor().findElement(
- By.className("v-grid-editor-save"));
+ protected void assertEditorClosed() {
+ assertNull("Editor is supposed to be closed", getEditor());
+ }
- saveButton.click();
+ protected List<WebElement> getEditorWidgets() {
+ assertNotNull("Editor is supposed to be open", getEditor());
+ return getEditor().findElements(By.className("v-textfield"));
- assertEquals("(100, 0) changed", getGridElement().getCell(100, 0)
- .getText());
}
@Test
- public void testProgrammaticSave() {
- selectMenuPath(EDIT_ITEM_100);
-
- WebElement textField = getEditorWidgets().get(0);
+ public void testFocusOnMouseOpen() {
- textField.click();
+ GridCellElement cell = getGridElement().getCell(4, 2);
- textField.sendKeys(" changed");
+ cell.doubleClick();
- selectMenuPath("Component", "Editor", "Save");
+ WebElement focused = getFocusedElement();
- assertEquals("(100, 0) changed", getGridElement().getCell(100, 0)
- .getText());
+ assertEquals("", "input", focused.getTagName());
+ assertEquals("", cell.getText(), focused.getAttribute("value"));
}
@Test
- public void testCaptionChange() {
- selectMenuPath(EDIT_ITEM_5);
- assertEquals("Save button caption should've been \""
- + GridConstants.DEFAULT_SAVE_CAPTION + "\" to begin with",
- GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText());
- assertEquals("Cancel button caption should've been \""
- + GridConstants.DEFAULT_CANCEL_CAPTION + "\" to begin with",
- GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton()
- .getText());
-
- selectMenuPath("Component", "Editor", "Change save caption");
- assertNotEquals(
- "Save button caption should've changed while editor is open",
- GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText());
-
- getCancelButton().click();
-
- selectMenuPath("Component", "Editor", "Change cancel caption");
- selectMenuPath(EDIT_ITEM_5);
- assertNotEquals(
- "Cancel button caption should've changed while editor is closed",
- GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton()
- .getText());
- }
+ public void testFocusOnKeyboardOpen() {
- private void assertEditorOpen() {
- assertNotNull("Editor is supposed to be open", getEditor());
- assertEquals("Unexpected number of widgets",
- GridBasicFeatures.EDITABLE_COLUMNS, getEditorWidgets().size());
- }
+ GridCellElement cell = getGridElement().getCell(4, 2);
- private void assertEditorClosed() {
- assertNull("Editor is supposed to be closed", getEditor());
- }
+ cell.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
- private List<WebElement> getEditorWidgets() {
- assertNotNull(getEditor());
- return getEditor().findElements(By.className("v-textfield"));
+ WebElement focused = getFocusedElement();
+ assertEquals("", "input", focused.getTagName());
+ assertEquals("", cell.getText(), focused.getAttribute("value"));
}
@Test
- public void testInvalidEdition() {
- selectMenuPath(EDIT_ITEM_5);
- assertFalse(logContainsText("Exception occured, java.lang.IllegalStateException"));
-
- GridEditorElement editor = getGridElement().getEditor();
+ public void testFocusOnProgrammaticOpenOnItemClick() {
+ selectMenuPath("Component", "State", "EditorOpeningItemClickListener");
- assertFalse(
- "Field 7 should not have been marked with an error before error",
- editor.isFieldErrorMarked(7));
+ GridCellElement cell = getGridElement().getCell(4, 2);
- WebElement intField = editor.getField(7);
- intField.clear();
- intField.sendKeys("banana phone");
- editor.save();
+ cell.click();
- assertEquals("Column 7: Could not convert value to Integer",
- editor.getErrorMessage());
- assertTrue("Field 7 should have been marked with an error after error",
- editor.isFieldErrorMarked(7));
- editor.cancel();
+ WebElement focused = getFocusedElement();
- selectMenuPath(EDIT_ITEM_100);
- assertFalse("Exception should not exist",
- isElementPresent(NotificationElement.class));
- assertEquals("There should be no editor error message", null,
- getGridElement().getEditor().getErrorMessage());
+ assertEquals("", "input", focused.getTagName());
+ assertEquals("", cell.getText(), focused.getAttribute("value"));
}
@Test
- public void testNoScrollAfterProgrammaticOpen() {
- int originalScrollPos = getGridVerticalScrollPos();
+ public void testNoFocusOnProgrammaticOpen() {
selectMenuPath(EDIT_ITEM_5);
- scrollGridVerticallyTo(100);
- assertEquals("Grid shouldn't scroll vertically while editing",
- originalScrollPos, getGridVerticalScrollPos());
+ WebElement focused = getFocusedElement();
+
+ assertEquals("Focus should remain in the menu", "menu",
+ focused.getAttribute("id"));
}
@Test
- public void testNoScrollAfterMouseOpen() {
- int originalScrollPos = getGridVerticalScrollPos();
+ public void testUneditableColumn() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
- GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
- new Actions(getDriver()).doubleClick(cell_5_0).perform();
+ GridEditorElement editor = getGridElement().getEditor();
+ assertFalse("Uneditable column should not have an editor widget",
+ editor.isEditable(3));
- scrollGridVerticallyTo(100);
- assertEquals("Grid shouldn't scroll vertically while editing",
- originalScrollPos, getGridVerticalScrollPos());
- }
+ String classNames = editor
+ .findElements(By.className("v-grid-editor-cells")).get(1)
+ .findElements(By.xpath("./div")).get(3).getAttribute("class");
- @Test
- public void testNoScrollAfterKeyboardOpen() {
- int originalScrollPos = getGridVerticalScrollPos();
+ assertTrue("Noneditable cell should contain not-editable classname",
+ classNames.contains("not-editable"));
- GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
- cell_5_0.click();
- new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertTrue("Noneditable cell should contain v-grid-cell classname",
+ classNames.contains("v-grid-cell"));
- scrollGridVerticallyTo(100);
- assertEquals("Grid shouldn't scroll vertically while editing",
- originalScrollPos, getGridVerticalScrollPos());
+ assertNoErrorNotifications();
}
@Test
- public void testEditorInDisabledGrid() {
- int originalScrollPos = getGridVerticalScrollPos();
+ public void testNoOpenFromHeaderOrFooter() {
+ selectMenuPath("Component", "Footer", "Visible");
- selectMenuPath(EDIT_ITEM_5);
- assertEditorOpen();
+ getGridElement().getHeaderCell(0, 0).doubleClick();
+ assertEditorClosed();
- selectMenuPath("Component", "State", "Enabled");
- assertEditorOpen();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertEditorClosed();
- GridEditorElement editor = getGridElement().getEditor();
- editor.save();
- assertEditorOpen();
+ getGridElement().getFooterCell(0, 0).doubleClick();
+ assertEditorClosed();
+
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertEditorClosed();
+ }
- editor.cancel();
+ public void testEditorMoveOnResize() {
+ selectMenuPath("Component", "Size", "Height", "500px");
+ getGridElement().getCell(22, 0).doubleClick();
assertEditorOpen();
- selectMenuPath("Component", "State", "Enabled");
+ GridEditorElement editor = getGridElement().getEditor();
+ TestBenchElement tableWrapper = getGridElement().getTableWrapper();
- scrollGridVerticallyTo(100);
- assertEquals("Grid shouldn't scroll vertically while editing",
- originalScrollPos, getGridVerticalScrollPos());
- }
+ int tableWrapperBottom = tableWrapper.getLocation().getY()
+ + tableWrapper.getSize().getHeight();
+ int editorBottom = editor.getLocation().getY()
+ + editor.getSize().getHeight();
- @Test
- public void testFocusOnMouseOpen() {
+ assertTrue("Editor should not be initially outside grid",
+ tableWrapperBottom - editorBottom <= 2);
- GridCellElement cell = getGridElement().getCell(4, 2);
+ selectMenuPath("Component", "Size", "Height", "300px");
+ assertEditorOpen();
- cell.doubleClick();
+ tableWrapperBottom = tableWrapper.getLocation().getY()
+ + tableWrapper.getSize().getHeight();
+ editorBottom = editor.getLocation().getY()
+ + editor.getSize().getHeight();
- WebElement focused = getFocusedElement();
-
- assertEquals("", "input", focused.getTagName());
- assertEquals("", cell.getText(), focused.getAttribute("value"));
+ assertTrue("Editor should not be outside grid after resize",
+ tableWrapperBottom - editorBottom <= 2);
}
- @Test
- public void testFocusOnKeyboardOpen() {
+ public void testEditorDoesNotMoveOnResizeIfNotNeeded() {
+ selectMenuPath("Component", "Size", "Height", "500px");
- GridCellElement cell = getGridElement().getCell(4, 2);
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
- cell.click();
- new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ GridEditorElement editor = getGridElement().getEditor();
- WebElement focused = getFocusedElement();
+ int editorPos = editor.getLocation().getY();
- assertEquals("", "input", focused.getTagName());
- assertEquals("", cell.getText(), focused.getAttribute("value"));
+ selectMenuPath("Component", "Size", "Height", "300px");
+ assertEditorOpen();
+
+ assertTrue("Editor should not have moved due to resize",
+ editorPos == editor.getLocation().getY());
}
@Test
- public void testNoFocusOnProgrammaticOpen() {
-
+ public void testEditorClosedOnSort() {
selectMenuPath(EDIT_ITEM_5);
- WebElement focused = getFocusedElement();
-
- assertEquals("Focus should remain in the menu", "menu",
- focused.getAttribute("id"));
- }
+ selectMenuPath("Component", "State", "Sort by column", "Column 0, ASC");
- @Override
- protected WebElement getFocusedElement() {
- return (WebElement) executeScript("return document.activeElement;");
+ assertEditorClosed();
}
@Test
- public void testUneditableColumn() {
+ public void testEditorClosedOnFilter() {
selectMenuPath(EDIT_ITEM_5);
- assertEditorOpen();
- GridEditorElement editor = getGridElement().getEditor();
- assertFalse("Uneditable column should not have an editor widget",
- editor.isEditable(3));
- assertEquals(
- "Not editable cell did not contain correct classname",
- "not-editable",
- editor.findElements(By.className("v-grid-editor-cells")).get(1)
- .findElements(By.xpath("./div")).get(3)
- .getAttribute("class"));
+ selectMenuPath("Component", "Filter", "Column 1 starts with \"(23\"");
- assertNoErrorNotifications();
+ assertEditorClosed();
}
- private WebElement getSaveButton() {
- return getDriver().findElement(By.className("v-grid-editor-save"));
+ protected WebElement getSaveButton() {
+ return getDriver().findElement(BY_EDITOR_SAVE);
}
- private WebElement getCancelButton() {
- return getDriver().findElement(By.className("v-grid-editor-cancel"));
+ protected WebElement getCancelButton() {
+ return getDriver().findElement(BY_EDITOR_CANCEL);
}
}
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java
new file mode 100644
index 0000000000..08094b57e3
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.tests.components.grid.basicfeatures.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+
+public class GridEditorUnbufferedTest extends GridEditorTest {
+
+ private static final String[] TOGGLE_EDITOR_BUFFERED = new String[] {
+ "Component", "Editor", "Buffered mode" };
+ private static final String[] CANCEL_EDIT = new String[] { "Component",
+ "Editor", "Cancel edit" };
+
+ @Override
+ @Before
+ public void setUp() {
+ super.setUp();
+ selectMenuPath(TOGGLE_EDITOR_BUFFERED);
+ }
+
+ @Test
+ public void testEditorShowsNoButtons() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ assertEditorOpen();
+
+ assertFalse("Save button should not be visible in unbuffered mode.",
+ isElementPresent(BY_EDITOR_SAVE));
+
+ assertFalse("Cancel button should not be visible in unbuffered mode.",
+ isElementPresent(BY_EDITOR_CANCEL));
+ }
+
+ @Test
+ public void testToggleEditorUnbufferedWhileOpen() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
+ selectMenuPath(TOGGLE_EDITOR_BUFFERED);
+ boolean thrown = logContainsText("Exception occured, java.lang.IllegalStateException");
+ assertTrue("IllegalStateException thrown", thrown);
+ }
+
+ @Test
+ public void testEditorMove() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ assertEditorOpen();
+
+ String firstFieldValue = getEditorWidgets().get(0)
+ .getAttribute("value");
+ assertEquals("Editor should be at row 5", "(5, 0)", firstFieldValue);
+
+ getGridElement().getCell(10, 0).click();
+ firstFieldValue = getEditorWidgets().get(0).getAttribute("value");
+
+ assertEquals("Editor should be at row 10", "(10, 0)", firstFieldValue);
+ }
+
+ @Test
+ public void testValidationErrorPreventsMove() {
+ // Because of "out of view" issues, we need to move this for easy access
+ selectMenuPath("Component", "Columns", "Column 7", "Column 7 Width",
+ "50px");
+ for (int i = 0; i < 6; ++i) {
+ selectMenuPath("Component", "Columns", "Column 7", "Move left");
+ }
+
+ selectMenuPath(EDIT_ITEM_5);
+
+ getEditorWidgets().get(1).click();
+ getEditorWidgets().get(1).sendKeys("not a number");
+
+ getGridElement().getCell(10, 0).click();
+
+ assertEquals("Editor should not move from row 5", "(5, 0)",
+ getEditorWidgets().get(0).getAttribute("value"));
+ }
+
+ @Test
+ public void testErrorMessageWrapperHidden() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ assertEditorOpen();
+
+ WebElement editorFooter = getEditor().findElement(
+ By.className("v-grid-editor-footer"));
+
+ assertTrue("Editor footer should not be visible when there's no error",
+ editorFooter.getCssValue("display").equalsIgnoreCase("none"));
+ }
+
+ @Test
+ public void testScrollEnabledOnProgrammaticOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ selectMenuPath(EDIT_ITEM_5);
+
+ scrollGridVerticallyTo(100);
+ assertGreater(
+ "Grid should scroll vertically while editing in unbuffered mode",
+ getGridVerticalScrollPos(), originalScrollPos);
+ }
+
+ @Test
+ public void testScrollEnabledOnMouseOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ new Actions(getDriver()).doubleClick(cell_5_0).perform();
+
+ scrollGridVerticallyTo(100);
+ assertGreater(
+ "Grid should scroll vertically while editing in unbuffered mode",
+ getGridVerticalScrollPos(), originalScrollPos);
+ }
+
+ @Test
+ public void testScrollEnabledOnKeyboardOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ cell_5_0.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ scrollGridVerticallyTo(100);
+ assertGreater(
+ "Grid should scroll vertically while editing in unbuffered mode",
+ getGridVerticalScrollPos(), originalScrollPos);
+ }
+
+ @Test
+ public void testEditorInDisabledGrid() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ selectMenuPath("Component", "State", "Enabled");
+ assertEditorOpen();
+
+ assertTrue("Editor text field should be disabled",
+ null != getEditorWidgets().get(2).getAttribute("disabled"));
+
+ selectMenuPath("Component", "State", "Enabled");
+ assertEditorOpen();
+
+ assertFalse("Editor text field should not be disabled",
+ null != getEditorWidgets().get(2).getAttribute("disabled"));
+ }
+
+ @Test
+ public void testMouseOpeningClosing() {
+
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorOpen();
+
+ selectMenuPath(CANCEL_EDIT);
+ selectMenuPath(TOGGLE_EDIT_ENABLED);
+
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorClosed();
+ }
+
+ @Test
+ public void testProgrammaticOpeningWhenOpen() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
+ assertEquals("Editor should edit row 5", "(5, 0)", getEditorWidgets()
+ .get(0).getAttribute("value"));
+
+ selectMenuPath(EDIT_ITEM_100);
+ assertEditorOpen();
+ assertEquals("Editor should edit row 100", "(100, 0)",
+ getEditorWidgets().get(0).getAttribute("value"));
+ }
+
+ @Test
+ public void testExternalValueChangePassesToEditor() {
+ selectMenuPath(EDIT_ITEM_5);
+ assertEditorOpen();
+
+ selectMenuPath("Component", "State", "ReactiveValueChanger");
+
+ getEditorWidgets().get(0).click();
+ getEditorWidgets().get(0).sendKeys("changing value");
+
+ // Focus another field to cause the value to be sent to the server
+ getEditorWidgets().get(2).click();
+
+ assertEquals("Value of Column 2 in the editor was not changed",
+ "Modified", getEditorWidgets().get(2).getAttribute("value"));
+ }
+
+ @Test
+ public void testEditorClosedOnUserSort() {
+ selectMenuPath(EDIT_ITEM_5);
+
+ getGridElement().getHeaderCell(0, 0).click();
+
+ assertEditorClosed();
+ }
+} \ No newline at end of file
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java
new file mode 100644
index 0000000000..ca9d78409c
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.tests.components.grid.basicfeatures.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.MenuBarElement;
+import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest;
+
+/**
+ * Test for server-side Grid focus features.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class GridFocusTest extends GridBasicFeaturesTest {
+
+ @Before
+ public void setUp() {
+ openTestURL();
+ }
+
+ @Test
+ public void testFocusListener() {
+ selectMenuPath("Component", "Listeners", "Focus listener");
+
+ getGridElement().click();
+
+ assertTrue("Focus listener should be invoked",
+ getLogRow(0).contains("FocusEvent"));
+ }
+
+ @Test
+ public void testBlurListener() {
+ selectMenuPath("Component", "Listeners", "Blur listener");
+
+ getGridElement().click();
+ $(MenuBarElement.class).first().click();
+
+ assertTrue("Blur listener should be invoked",
+ getLogRow(0).contains("BlurEvent"));
+ }
+
+ @Test
+ public void testProgrammaticFocus() {
+ selectMenuPath("Component", "State", "Set focus");
+
+ assertTrue("Grid cell (0, 0) should be focused", getGridElement()
+ .getCell(0, 0).isFocused());
+ }
+
+ @Test
+ public void testTabIndex() {
+ assertEquals(getGridElement().getAttribute("tabindex"), "0");
+
+ selectMenuPath("Component", "State", "Tab index", "-1");
+ assertEquals(getGridElement().getAttribute("tabindex"), "-1");
+
+ selectMenuPath("Component", "State", "Tab index", "10");
+ assertEquals(getGridElement().getAttribute("tabindex"), "10");
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java
index 5d72d481c7..f44f39689c 100644
--- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java
+++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java
@@ -274,7 +274,7 @@ public class GridStructureTest extends GridBasicFeaturesTest {
@Test
public void testBareItemSetChangeRemovingAllRows() throws Exception {
openTestURL();
- selectMenuPath("Component", "Filter", "Add impassable filter");
+ selectMenuPath("Component", "Filter", "Impassable filter");
assertFalse("A notification shouldn't have been displayed",
$(NotificationElement.class).exists());
assertTrue("No body cells should've been found", getGridElement()