summaryrefslogtreecommitdiffstats
path: root/compatibility-client
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2016-08-18 18:04:59 +0300
committerArtur Signell <artur@vaadin.com>2016-08-20 00:08:44 +0300
commit6e0f2efe996cfd3b38c960e04cbced0a91215cf0 (patch)
tree9eafac7107b3d34d8d1e9bc7631a1debad4fd720 /compatibility-client
parent016a2ceb86f771a3902ace98d9fe56351c0efdf2 (diff)
downloadvaadin-framework-6e0f2efe996cfd3b38c960e04cbced0a91215cf0.tar.gz
vaadin-framework-6e0f2efe996cfd3b38c960e04cbced0a91215cf0.zip
Move FieldGroup and Vaadin 7 Grid to compatibility package
Change-Id: I9aaef478e0b67462641239802b924b8461cb9225
Diffstat (limited to 'compatibility-client')
-rw-r--r--compatibility-client/pom.xml27
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractGridRendererConnector.java84
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractSelectionModelConnector.java82
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/ButtonRendererConnector.java45
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/ClickableRendererConnector.java63
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/DateRendererConnector.java34
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/DetailComponentManagerConnector.java37
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/GridConnector.java1311
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/ImageRendererConnector.java57
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/JavaScriptRendererConnector.java278
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java386
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/NoSelectionModelConnector.java45
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/NumberRendererConnector.java34
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/ProgressBarRendererConnector.java35
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/RpcDataSourceConnector.java253
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java180
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/TextRendererConnector.java34
-rw-r--r--compatibility-client/src/main/java/com/vaadin/client/connectors/UnsafeHtmlRendererConnector.java43
-rwxr-xr-xcompatibility-client/src/main/resources/com/vaadin/Vaadin7WidgetSet.gwt.xml (renamed from compatibility-client/src/main/resources/com/vaadin/v7/Vaadin7WidgetSet.gwt.xml)0
19 files changed, 3028 insertions, 0 deletions
diff --git a/compatibility-client/pom.xml b/compatibility-client/pom.xml
index 868dccf7d9..bab3989642 100644
--- a/compatibility-client/pom.xml
+++ b/compatibility-client/pom.xml
@@ -43,6 +43,33 @@
</executions>
</plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <!-- Copy .java files to package -->
+ <execution>
+ <id>copy-sources</id>
+ <!-- here the phase you need -->
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.outputDirectory}</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>false</filtering>
+ </resource>
+ <resource>
+ <directory>src/main/java</directory>
+ <filtering>false</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractGridRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractGridRendererConnector.java
new file mode 100644
index 0000000000..7824e41dd7
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractGridRendererConnector.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widgets.Grid.Column;
+
+import elemental.json.JsonObject;
+
+/**
+ * An abstract base class for renderer connectors. A renderer connector is used
+ * to link a client-side {@link Renderer} to a server-side
+ * {@link com.vaadin.ui.components.grid.Renderer Renderer}. As a connector, it
+ * can use the regular Vaadin RPC and shared state mechanism to pass additional
+ * state and information between the client and the server. This base class
+ * itself only uses the basic {@link com.vaadin.shared.communication.SharedState
+ * SharedState} and no RPC interfaces.
+ *
+ * @param <T>
+ * the presentation type of the renderer
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractGridRendererConnector<T>
+ extends AbstractRendererConnector<T> {
+
+ /**
+ * Gets the row key for a row object.
+ * <p>
+ * In case this renderer wants be able to identify a row in such a way that
+ * the server also understands it, the row key is used for that. Rows are
+ * identified by unified keys between the client and the server.
+ *
+ * @param row
+ * the row object
+ * @return the row key for the given row
+ */
+ protected String getRowKey(JsonObject row) {
+ final ServerConnector parent = getParent();
+ if (parent instanceof GridConnector) {
+ return ((GridConnector) parent).getRowKey(row);
+ } else {
+ throw new IllegalStateException(
+ "Renderers can only be used " + "with a Grid.");
+ }
+ }
+
+ /**
+ * Gets the column id for a column.
+ * <p>
+ * In case this renderer wants be able to identify a column in such a way
+ * that the server also understands it, the column id is used for that.
+ * Columns are identified by unified ids between the client and the server.
+ *
+ * @param column
+ * the column object
+ * @return the column id for the given column
+ */
+ protected String getColumnId(Column<?, JsonObject> column) {
+ final ServerConnector parent = getParent();
+ if (parent instanceof GridConnector) {
+ return ((GridConnector) parent).getColumnId(column);
+ } else {
+ throw new IllegalStateException(
+ "Renderers can only be used " + "with a Grid.");
+ }
+ }
+
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractSelectionModelConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
new file mode 100644
index 0000000000..75664d04f9
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import java.util.Collection;
+
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.shared.ui.grid.GridState;
+
+import elemental.json.JsonObject;
+
+/**
+ * Base class for all selection model connectors.
+ *
+ * @since 7.6
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractSelectionModelConnector<T extends SelectionModel<JsonObject>>
+ extends AbstractExtensionConnector {
+
+ @Override
+ public GridConnector getParent() {
+ return (GridConnector) super.getParent();
+ }
+
+ protected Grid<JsonObject> getGrid() {
+ return getParent().getWidget();
+ }
+
+ protected RowHandle<JsonObject> getRowHandle(JsonObject row) {
+ return getGrid().getDataSource().getHandle(row);
+ }
+
+ protected String getRowKey(JsonObject row) {
+ return row != null ? getParent().getRowKey(row) : null;
+ }
+
+ protected abstract T createSelectionModel();
+
+ public abstract static class AbstractSelectionModel
+ implements SelectionModel<JsonObject> {
+
+ @Override
+ public boolean isSelected(JsonObject row) {
+ return row.hasKey(GridState.JSONKEY_SELECTED);
+ }
+
+ @Override
+ public void setGrid(Grid<JsonObject> grid) {
+ // NO-OP
+ }
+
+ @Override
+ public void reset() {
+ // Should not need any actions.
+ }
+
+ @Override
+ public Collection<JsonObject> getSelectedRows() {
+ throw new UnsupportedOperationException(
+ "This client-side selection model "
+ + getClass().getSimpleName()
+ + " does not know selected rows.");
+ }
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/ButtonRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/ButtonRendererConnector.java
new file mode 100644
index 0000000000..fad8918c92
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/ButtonRendererConnector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.vaadin.client.renderers.ButtonRenderer;
+import com.vaadin.client.renderers.ClickableRenderer.RendererClickHandler;
+import com.vaadin.shared.ui.Connect;
+
+import elemental.json.JsonObject;
+
+/**
+ * A connector for {@link ButtonRenderer}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.ButtonRenderer.class)
+public class ButtonRendererConnector
+ extends ClickableRendererConnector<String> {
+
+ @Override
+ public ButtonRenderer getRenderer() {
+ return (ButtonRenderer) super.getRenderer();
+ }
+
+ @Override
+ protected HandlerRegistration addClickHandler(
+ RendererClickHandler<JsonObject> handler) {
+ return getRenderer().addClickHandler(handler);
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/ClickableRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/ClickableRendererConnector.java
new file mode 100644
index 0000000000..89549bc2bc
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/ClickableRendererConnector.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.renderers.ClickableRenderer;
+import com.vaadin.client.renderers.ClickableRenderer.RendererClickEvent;
+import com.vaadin.client.renderers.ClickableRenderer.RendererClickHandler;
+import com.vaadin.shared.ui.grid.renderers.RendererClickRpc;
+
+import elemental.json.JsonObject;
+
+/**
+ * An abstract base class for {@link ClickableRenderer} connectors.
+ *
+ * @param <T>
+ * the presentation type of the renderer
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public abstract class ClickableRendererConnector<T>
+ extends AbstractGridRendererConnector<T> {
+
+ HandlerRegistration clickRegistration;
+
+ @Override
+ protected void init() {
+ clickRegistration = addClickHandler(
+ new RendererClickHandler<JsonObject>() {
+ @Override
+ public void onClick(RendererClickEvent<JsonObject> event) {
+ getRpcProxy(RendererClickRpc.class).click(
+ getRowKey(event.getCell().getRow()),
+ getColumnId(event.getCell().getColumn()),
+ MouseEventDetailsBuilder.buildMouseEventDetails(
+ event.getNativeEvent()));
+ }
+ });
+ }
+
+ @Override
+ public void onUnregister() {
+ clickRegistration.removeHandler();
+ }
+
+ protected abstract HandlerRegistration addClickHandler(
+ RendererClickHandler<JsonObject> handler);
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/DateRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/DateRendererConnector.java
new file mode 100644
index 0000000000..4b8c3872da
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/DateRendererConnector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * A connector for {@link com.vaadin.ui.components.grid.renderers.DateRenderer
+ * DateRenderer}.
+ * <p>
+ * The server-side Renderer operates on dates, but the data is serialized as a
+ * string, and displayed as-is on the client side. This is to be able to support
+ * the server's locale.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.DateRenderer.class)
+public class DateRendererConnector extends TextRendererConnector {
+ // No implementation needed
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/DetailComponentManagerConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/DetailComponentManagerConnector.java
new file mode 100644
index 0000000000..000a24af00
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/DetailComponentManagerConnector.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.ui.LegacyGrid.DetailComponentManager;
+
+/**
+ * Client-side connector for the DetailComponentManager of Grid.
+ *
+ * @since 7.6.1
+ */
+@Connect(DetailComponentManager.class)
+public class DetailComponentManagerConnector
+ extends AbstractExtensionConnector {
+
+ @Override
+ protected void extend(ServerConnector target) {
+ // TODO: Move DetailsGenerator logic here.
+ }
+
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/GridConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/GridConnector.java
new file mode 100644
index 0000000000..045f51f508
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/GridConnector.java
@@ -0,0 +1,1311 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.connectors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorHierarchyChangeEvent;
+import com.vaadin.client.DeferredWorker;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
+import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener;
+import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.AbstractHasComponentsConnector;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
+import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.widget.escalator.events.RowHeightChangedEvent;
+import com.vaadin.client.widget.escalator.events.RowHeightChangedHandler;
+import com.vaadin.client.widget.grid.CellReference;
+import com.vaadin.client.widget.grid.CellStyleGenerator;
+import com.vaadin.client.widget.grid.EditorHandler;
+import com.vaadin.client.widget.grid.EventCellReference;
+import com.vaadin.client.widget.grid.HeightAwareDetailsGenerator;
+import com.vaadin.client.widget.grid.RowReference;
+import com.vaadin.client.widget.grid.RowStyleGenerator;
+import com.vaadin.client.widget.grid.events.BodyClickHandler;
+import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
+import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
+import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
+import com.vaadin.client.widget.grid.events.ColumnResizeEvent;
+import com.vaadin.client.widget.grid.events.ColumnResizeHandler;
+import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
+import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
+import com.vaadin.client.widget.grid.events.GridClickEvent;
+import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
+import com.vaadin.client.widget.grid.sort.SortEvent;
+import com.vaadin.client.widget.grid.sort.SortHandler;
+import com.vaadin.client.widget.grid.sort.SortOrder;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.Column;
+import com.vaadin.client.widgets.Grid.FooterCell;
+import com.vaadin.client.widgets.Grid.FooterRow;
+import com.vaadin.client.widgets.Grid.HeaderCell;
+import com.vaadin.client.widgets.Grid.HeaderRow;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.data.sort.SortDirection;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.EditorClientRpc;
+import com.vaadin.shared.ui.grid.EditorServerRpc;
+import com.vaadin.shared.ui.grid.GridClientRpc;
+import com.vaadin.shared.ui.grid.GridColumnState;
+import com.vaadin.shared.ui.grid.GridConstants;
+import com.vaadin.shared.ui.grid.GridConstants.Section;
+import com.vaadin.shared.ui.grid.GridServerRpc;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.GridStaticSectionState;
+import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
+import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
+import com.vaadin.shared.ui.grid.ScrollDestination;
+import com.vaadin.ui.LegacyGrid;
+
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+/**
+ * Connects the client side {@link Grid} widget with the server side
+ * {@link com.vaadin.ui.components.grid.Grid} component.
+ * <p>
+ * The Grid is typed to JSONObject. The structure of the JSONObject is described
+ * at {@link com.vaadin.shared.data.DataProviderRpc#setRowData(int, List)
+ * DataProviderRpc.setRowData(int, List)}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(LegacyGrid.class)
+public class GridConnector extends AbstractHasComponentsConnector
+ implements SimpleManagedLayout, DeferredWorker {
+
+ private static final class CustomStyleGenerator implements
+ CellStyleGenerator<JsonObject>, RowStyleGenerator<JsonObject> {
+ @Override
+ public String getStyle(CellReference<JsonObject> cellReference) {
+ JsonObject row = cellReference.getRow();
+ if (!row.hasKey(GridState.JSONKEY_CELLSTYLES)) {
+ return null;
+ }
+
+ Column<?, JsonObject> column = cellReference.getColumn();
+ if (!(column instanceof CustomGridColumn)) {
+ // Selection checkbox column
+ return null;
+ }
+ CustomGridColumn c = (CustomGridColumn) column;
+
+ JsonObject cellStylesObject = row
+ .getObject(GridState.JSONKEY_CELLSTYLES);
+ assert cellStylesObject != null;
+
+ if (cellStylesObject.hasKey(c.id)) {
+ return cellStylesObject.getString(c.id);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String getStyle(RowReference<JsonObject> rowReference) {
+ JsonObject row = rowReference.getRow();
+ if (row.hasKey(GridState.JSONKEY_ROWSTYLE)) {
+ return row.getString(GridState.JSONKEY_ROWSTYLE);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Custom implementation of the custom grid column using a JSONObject to
+ * represent the cell value and String as a column type.
+ */
+ private class CustomGridColumn extends Grid.Column<Object, JsonObject> {
+
+ private final String id;
+
+ private AbstractGridRendererConnector<Object> rendererConnector;
+
+ private AbstractComponentConnector editorConnector;
+
+ private HandlerRegistration errorStateHandler;
+
+ public CustomGridColumn(String id,
+ AbstractGridRendererConnector<Object> rendererConnector) {
+ super(rendererConnector.getRenderer());
+ this.rendererConnector = rendererConnector;
+ this.id = id;
+ }
+
+ /**
+ * Sets a new renderer for this column object
+ *
+ * @param rendererConnector
+ * a renderer connector object
+ */
+ public void setRenderer(
+ AbstractGridRendererConnector<Object> rendererConnector) {
+ setRenderer(rendererConnector.getRenderer());
+ this.rendererConnector = rendererConnector;
+ }
+
+ @Override
+ public Object getValue(final JsonObject obj) {
+ final JsonObject rowData = obj.getObject(GridState.JSONKEY_DATA);
+
+ if (rowData.hasKey(id)) {
+ final JsonValue columnValue = rowData.get(id);
+
+ return rendererConnector.decode(columnValue);
+ }
+
+ return null;
+ }
+
+ private AbstractComponentConnector getEditorConnector() {
+ return editorConnector;
+ }
+
+ private void setEditorConnector(
+ final AbstractComponentConnector editorConnector) {
+ this.editorConnector = editorConnector;
+
+ if (errorStateHandler != null) {
+ errorStateHandler.removeHandler();
+ errorStateHandler = null;
+ }
+
+ // Avoid nesting too deep
+ if (editorConnector == null) {
+ return;
+ }
+
+ errorStateHandler = editorConnector.addStateChangeHandler(
+ "errorMessage", new StateChangeHandler() {
+
+ @Override
+ public void onStateChanged(
+ StateChangeEvent stateChangeEvent) {
+
+ String error = editorConnector
+ .getState().errorMessage;
+
+ if (error == null) {
+ columnToErrorMessage
+ .remove(CustomGridColumn.this);
+ } else {
+ // The error message is formatted as HTML;
+ // therefore, we use this hack to make the
+ // string human-readable.
+ Element e = DOM.createElement("div");
+ e.setInnerHTML(editorConnector
+ .getState().errorMessage);
+ error = getHeaderCaption() + ": "
+ + e.getInnerText();
+
+ columnToErrorMessage.put(CustomGridColumn.this,
+ error);
+ }
+
+ // Handle Editor RPC before updating error status
+ Scheduler.get()
+ .scheduleFinally(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ updateErrorColumns();
+ }
+ });
+ }
+
+ public void updateErrorColumns() {
+ getWidget().getEditor().setEditorError(
+ getColumnErrors(),
+ columnToErrorMessage.keySet());
+ }
+ });
+ }
+ }
+
+ /*
+ * An editor handler using Vaadin RPC to manage the editor state.
+ */
+ private class CustomEditorHandler implements EditorHandler<JsonObject> {
+
+ private EditorServerRpc rpc = getRpcProxy(EditorServerRpc.class);
+
+ private EditorRequest<JsonObject> currentRequest = null;
+ private boolean serverInitiated = false;
+
+ public CustomEditorHandler() {
+ registerRpc(EditorClientRpc.class, new EditorClientRpc() {
+
+ @Override
+ public void bind(final int rowIndex) {
+ // call this deferred to avoid issues with editing on init
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ GridConnector.this.getWidget().editRow(rowIndex);
+ }
+ });
+ }
+
+ @Override
+ public void cancel(int rowIndex) {
+ serverInitiated = true;
+ GridConnector.this.getWidget().cancelEditor();
+ }
+
+ @Override
+ public void confirmBind(final boolean bindSucceeded) {
+ endRequest(bindSucceeded, null, null);
+ }
+
+ @Override
+ public void confirmSave(boolean saveSucceeded,
+ String errorMessage, List<String> errorColumnsIds) {
+ endRequest(saveSucceeded, errorMessage, errorColumnsIds);
+ }
+ });
+ }
+
+ @Override
+ public void bind(EditorRequest<JsonObject> request) {
+ startRequest(request);
+ rpc.bind(request.getRowIndex());
+ }
+
+ @Override
+ public void save(EditorRequest<JsonObject> request) {
+ startRequest(request);
+ rpc.save(request.getRowIndex());
+ }
+
+ @Override
+ public void cancel(EditorRequest<JsonObject> request) {
+ if (!handleServerInitiated(request)) {
+ // No startRequest as we don't get (or need)
+ // a confirmation from the server
+ rpc.cancel(request.getRowIndex());
+ }
+ }
+
+ @Override
+ public Widget getWidget(Grid.Column<?, JsonObject> column) {
+ assert column != null;
+
+ if (column instanceof CustomGridColumn) {
+ AbstractComponentConnector c = ((CustomGridColumn) column)
+ .getEditorConnector();
+
+ if (c == null) {
+ return null;
+ }
+
+ return c.getWidget();
+ } else {
+ throw new IllegalStateException("Unexpected column type: "
+ + column.getClass().getName());
+ }
+ }
+
+ /**
+ * Used to handle the case where the editor calls us because it was
+ * invoked by the server via RPC and not by the client. In that case,
+ * the request can be simply synchronously completed.
+ *
+ * @param request
+ * the request object
+ * @return true if the request was originally triggered by the server,
+ * false otherwise
+ */
+ private boolean handleServerInitiated(EditorRequest<?> request) {
+ assert request != null : "Cannot handle null request";
+ assert currentRequest == null : "Earlier request not yet finished";
+
+ if (serverInitiated) {
+ serverInitiated = false;
+ request.success();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void startRequest(EditorRequest<JsonObject> request) {
+ assert currentRequest == null : "Earlier request not yet finished";
+
+ currentRequest = request;
+ }
+
+ private void endRequest(boolean succeeded, String errorMessage,
+ List<String> errorColumnsIds) {
+ assert currentRequest != null : "Current request was null";
+ /*
+ * Clear current request first to ensure the state is valid if
+ * another request is made in the callback.
+ */
+ EditorRequest<JsonObject> request = currentRequest;
+ currentRequest = null;
+ if (succeeded) {
+ request.success();
+ } else {
+ Collection<Column<?, JsonObject>> errorColumns;
+ if (errorColumnsIds != null) {
+ errorColumns = new ArrayList<Grid.Column<?, JsonObject>>();
+ for (String colId : errorColumnsIds) {
+ errorColumns.add(columnIdToColumn.get(colId));
+ }
+ } else {
+ errorColumns = null;
+ }
+
+ request.failure(errorMessage, errorColumns);
+ }
+ }
+ }
+
+ private class ItemClickHandler
+ implements BodyClickHandler, BodyDoubleClickHandler {
+
+ @Override
+ public void onClick(GridClickEvent event) {
+ if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
+ fireItemClick(event.getTargetCell(), event.getNativeEvent());
+ }
+ }
+
+ @Override
+ public void onDoubleClick(GridDoubleClickEvent event) {
+ if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
+ fireItemClick(event.getTargetCell(), event.getNativeEvent());
+ }
+ }
+
+ private void fireItemClick(CellReference<?> cell,
+ NativeEvent mouseEvent) {
+ String rowKey = getRowKey((JsonObject) cell.getRow());
+ String columnId = getColumnId(cell.getColumn());
+ getRpcProxy(GridServerRpc.class).itemClick(rowKey, columnId,
+ MouseEventDetailsBuilder
+ .buildMouseEventDetails(mouseEvent));
+ }
+ }
+
+ private ColumnReorderHandler<JsonObject> columnReorderHandler = new ColumnReorderHandler<JsonObject>() {
+
+ @Override
+ public void onColumnReorder(ColumnReorderEvent<JsonObject> event) {
+ if (!columnsUpdatedFromState) {
+ List<Column<?, JsonObject>> columns = getWidget().getColumns();
+ final List<String> newColumnOrder = new ArrayList<String>();
+ for (Column<?, JsonObject> column : columns) {
+ if (column instanceof CustomGridColumn) {
+ newColumnOrder.add(((CustomGridColumn) column).id);
+ } // the other case would be the multi selection column
+ }
+ getRpcProxy(GridServerRpc.class)
+ .columnsReordered(newColumnOrder, columnOrder);
+ columnOrder = newColumnOrder;
+ getState().columnOrder = newColumnOrder;
+ }
+ }
+ };
+
+ private ColumnVisibilityChangeHandler<JsonObject> columnVisibilityChangeHandler = new ColumnVisibilityChangeHandler<JsonObject>() {
+
+ @Override
+ public void onVisibilityChange(
+ ColumnVisibilityChangeEvent<JsonObject> event) {
+ if (!columnsUpdatedFromState) {
+ Column<?, JsonObject> column = event.getColumn();
+ if (column instanceof CustomGridColumn) {
+ getRpcProxy(GridServerRpc.class).columnVisibilityChanged(
+ ((CustomGridColumn) column).id, column.isHidden(),
+ event.isUserOriginated());
+ for (GridColumnState state : getState().columns) {
+ if (state.id.equals(((CustomGridColumn) column).id)) {
+ state.hidden = event.isHidden();
+ break;
+ }
+ }
+ } else {
+ getLogger().warning(
+ "Visibility changed for a unknown column type in Grid: "
+ + column.toString() + ", type "
+ + column.getClass());
+ }
+ }
+ }
+ };
+
+ private ColumnResizeHandler<JsonObject> columnResizeHandler = new ColumnResizeHandler<JsonObject>() {
+
+ @Override
+ public void onColumnResize(ColumnResizeEvent<JsonObject> event) {
+ if (!columnsUpdatedFromState) {
+ Column<?, JsonObject> column = event.getColumn();
+ if (column instanceof CustomGridColumn) {
+ getRpcProxy(GridServerRpc.class).columnResized(
+ ((CustomGridColumn) column).id,
+ column.getWidthActual());
+ }
+ }
+ }
+ };
+
+ private class CustomDetailsGenerator
+ implements HeightAwareDetailsGenerator {
+
+ private final Map<String, ComponentConnector> idToDetailsMap = new HashMap<String, ComponentConnector>();
+ private final Map<String, Integer> idToRowIndex = new HashMap<String, Integer>();
+
+ @Override
+ public Widget getDetails(int rowIndex) {
+ String id = getId(rowIndex);
+ if (id == null) {
+ return null;
+ }
+ ComponentConnector componentConnector = idToDetailsMap.get(id);
+ idToRowIndex.put(id, rowIndex);
+
+ return componentConnector.getWidget();
+ }
+
+ @Override
+ public double getDetailsHeight(int rowIndex) {
+ // Case of null is handled in the getDetails method and this method
+ // will not called if it returns null.
+ String id = getId(rowIndex);
+ ComponentConnector componentConnector = idToDetailsMap.get(id);
+
+ getLayoutManager().setNeedsMeasureRecursively(componentConnector);
+ getLayoutManager().layoutNow();
+
+ return getLayoutManager().getOuterHeightDouble(
+ componentConnector.getWidget().getElement());
+ }
+
+ /**
+ * Fetches id from the row object that corresponds with the given
+ * rowIndex.
+ *
+ * @since 7.6.1
+ * @param rowIndex
+ * the index of the row for which to fetch the id
+ * @return id of the row if such id exists, {@code null} otherwise
+ */
+ private String getId(int rowIndex) {
+ JsonObject row = getWidget().getDataSource().getRow(rowIndex);
+
+ if (!row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) || row
+ .getString(GridState.JSONKEY_DETAILS_VISIBLE).isEmpty()) {
+ return null;
+ }
+
+ return row.getString(GridState.JSONKEY_DETAILS_VISIBLE);
+ }
+
+ public void updateConnectorHierarchy(List<ServerConnector> children) {
+ Set<String> connectorIds = new HashSet<String>();
+ for (ServerConnector child : children) {
+ if (child instanceof ComponentConnector) {
+ connectorIds.add(child.getConnectorId());
+ idToDetailsMap.put(child.getConnectorId(),
+ (ComponentConnector) child);
+ }
+ }
+
+ Set<String> removedDetails = new HashSet<String>();
+ for (Entry<String, ComponentConnector> entry : idToDetailsMap
+ .entrySet()) {
+ ComponentConnector connector = entry.getValue();
+ String id = connector.getConnectorId();
+ if (!connectorIds.contains(id)) {
+ removedDetails.add(entry.getKey());
+ if (idToRowIndex.containsKey(id)) {
+ getWidget().setDetailsVisible(idToRowIndex.get(id),
+ false);
+ }
+ }
+ }
+
+ for (String id : removedDetails) {
+ idToDetailsMap.remove(id);
+ idToRowIndex.remove(id);
+ }
+ }
+ }
+
+ /**
+ * Class for handling scrolling issues with open details.
+ *
+ * @since 7.5.2
+ */
+ private class LazyDetailsScroller implements DeferredWorker {
+
+ /* Timer value tested to work in our test cluster with slow IE8s. */
+ private static final int DISABLE_LAZY_SCROLL_TIMEOUT = 1500;
+
+ /*
+ * Cancels details opening scroll after timeout. Avoids any unexpected
+ * scrolls via details opening.
+ */
+ private Timer disableScroller = new Timer() {
+ @Override
+ public void run() {
+ targetRow = -1;
+ }
+ };
+
+ private Integer targetRow = -1;
+ private ScrollDestination destination = null;
+
+ public void scrollToRow(Integer row, ScrollDestination dest) {
+ targetRow = row;
+ destination = dest;
+ disableScroller.schedule(DISABLE_LAZY_SCROLL_TIMEOUT);
+ }
+
+ /**
+ * Inform LazyDetailsScroller that a details row has opened on a row.
+ *
+ * @param rowIndex
+ * index of row with details now open
+ */
+ public void detailsOpened(int rowIndex) {
+ if (targetRow == rowIndex) {
+ getWidget().scrollToRow(targetRow, destination);
+ disableScroller.run();
+ }
+ }
+
+ @Override
+ public boolean isWorkPending() {
+ return disableScroller.isRunning();
+ }
+ }
+
+ /**
+ * Maps a generated column id to a grid column instance
+ */
+ private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
+
+ private List<String> columnOrder = new ArrayList<String>();
+
+ /**
+ * {@link #columnsUpdatedFromState} is set to true when
+ * {@link #updateColumnOrderFromState(List)} is updating the column order
+ * for the widget. This flag tells the {@link #columnReorderHandler} to not
+ * send same data straight back to server. After updates, listener sets the
+ * value back to false.
+ */
+ private boolean columnsUpdatedFromState;
+
+ private RpcDataSource dataSource;
+
+ /* Used to track Grid editor columns with validation errors */
+ private final Map<Column<?, JsonObject>, String> columnToErrorMessage = new HashMap<Column<?, JsonObject>, String>();
+
+ private ItemClickHandler itemClickHandler = new ItemClickHandler();
+
+ private String lastKnownTheme = null;
+
+ private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator();
+ private final CustomStyleGenerator styleGenerator = new CustomStyleGenerator();
+
+ private final DetailsListener detailsListener = new DetailsListener() {
+ @Override
+ public void reapplyDetailsVisibility(final int rowIndex,
+ final JsonObject row) {
+
+ if (hasDetailsOpen(row)) {
+ // Command for opening details row.
+ ScheduledCommand openDetails = new ScheduledCommand() {
+ @Override
+ public void execute() {
+ // Re-apply to force redraw.
+ getWidget().setDetailsVisible(rowIndex, false);
+ getWidget().setDetailsVisible(rowIndex, true);
+ lazyDetailsScroller.detailsOpened(rowIndex);
+ }
+ };
+
+ if (initialChange) {
+ Scheduler.get().scheduleDeferred(openDetails);
+ } else {
+ Scheduler.get().scheduleFinally(openDetails);
+ }
+ } else {
+ getWidget().setDetailsVisible(rowIndex, false);
+ }
+ }
+
+ private boolean hasDetailsOpen(JsonObject row) {
+ return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
+ && row.getString(GridState.JSONKEY_DETAILS_VISIBLE) != null;
+ }
+ };
+
+ private final LazyDetailsScroller lazyDetailsScroller = new LazyDetailsScroller();
+ private final CustomEditorHandler editorHandler = new CustomEditorHandler();
+
+ /*
+ * Initially details need to behave a bit differently to allow some
+ * escalator magic.
+ */
+ private boolean initialChange;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Grid<JsonObject> getWidget() {
+ return (Grid<JsonObject>) super.getWidget();
+ }
+
+ @Override
+ public GridState getState() {
+ return (GridState) super.getState();
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+
+ // All scroll RPC calls are executed finally to avoid issues on init
+ registerRpc(GridClientRpc.class, new GridClientRpc() {
+ @Override
+ public void scrollToStart() {
+ /*
+ * no need for lazyDetailsScrollAdjuster, because the start is
+ * always 0, won't change a bit.
+ */
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ getWidget().scrollToStart();
+ }
+ });
+ }
+
+ @Override
+ public void scrollToEnd() {
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ getWidget().scrollToEnd();
+ // Scrolls further if details opens.
+ lazyDetailsScroller.scrollToRow(dataSource.size() - 1,
+ ScrollDestination.END);
+ }
+ });
+ }
+
+ @Override
+ public void scrollToRow(final int row,
+ final ScrollDestination destination) {
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ getWidget().scrollToRow(row, destination);
+ // Scrolls a bit further if details opens.
+ lazyDetailsScroller.scrollToRow(row, destination);
+ }
+ });
+ }
+
+ @Override
+ public void recalculateColumnWidths() {
+ getWidget().recalculateColumnWidths();
+ }
+ });
+
+ /* Item click events */
+ getWidget().addBodyClickHandler(itemClickHandler);
+ getWidget().addBodyDoubleClickHandler(itemClickHandler);
+
+ /* Style Generators */
+ getWidget().setCellStyleGenerator(styleGenerator);
+ getWidget().setRowStyleGenerator(styleGenerator);
+
+ getWidget().addSortHandler(new SortHandler<JsonObject>() {
+ @Override
+ public void sort(SortEvent<JsonObject> event) {
+ List<SortOrder> order = event.getOrder();
+ String[] columnIds = new String[order.size()];
+ SortDirection[] directions = new SortDirection[order.size()];
+ for (int i = 0; i < order.size(); i++) {
+ SortOrder sortOrder = order.get(i);
+ CustomGridColumn column = (CustomGridColumn) sortOrder
+ .getColumn();
+ columnIds[i] = column.id;
+
+ directions[i] = sortOrder.getDirection();
+ }
+
+ if (!Arrays.equals(columnIds, getState().sortColumns)
+ || !Arrays.equals(directions, getState().sortDirs)) {
+ // Report back to server if changed
+ getRpcProxy(GridServerRpc.class).sort(columnIds, directions,
+ event.isUserOriginated());
+ }
+ }
+ });
+
+ getWidget().setEditorHandler(editorHandler);
+ getWidget().addColumnReorderHandler(columnReorderHandler);
+ getWidget().addColumnVisibilityChangeHandler(
+ columnVisibilityChangeHandler);
+ getWidget().addColumnResizeHandler(columnResizeHandler);
+
+ ConnectorFocusAndBlurHandler.addHandlers(this);
+
+ getWidget().setDetailsGenerator(customDetailsGenerator);
+ getLayoutManager().registerDependency(this, getWidget().getElement());
+
+ // Handling row height changes
+ getWidget().addRowHeightChangedHandler(new RowHeightChangedHandler() {
+ @Override
+ public void onRowHeightChanged(RowHeightChangedEvent event) {
+ getLayoutManager()
+ .setNeedsMeasureRecursively(GridConnector.this);
+ getLayoutManager().layoutNow();
+ }
+ });
+
+ layout();
+ }
+
+ @Override
+ public void onStateChanged(final StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ initialChange = stateChangeEvent.isInitialStateChange();
+
+ // Column updates
+ if (stateChangeEvent.hasPropertyChanged("columns")) {
+
+ // Remove old columns
+ purgeRemovedColumns();
+
+ // Add new columns
+ for (GridColumnState state : getState().columns) {
+ if (!columnIdToColumn.containsKey(state.id)) {
+ addColumnFromStateChangeEvent(state);
+ }
+ updateColumnFromStateChangeEvent(state);
+ }
+ }
+
+ if (stateChangeEvent.hasPropertyChanged("columnOrder")) {
+ if (orderNeedsUpdate(getState().columnOrder)) {
+ updateColumnOrderFromState(getState().columnOrder);
+ }
+ }
+
+ // Header and footer
+ if (stateChangeEvent.hasPropertyChanged("header")) {
+ updateHeaderFromState(getState().header);
+ }
+
+ if (stateChangeEvent.hasPropertyChanged("footer")) {
+ updateFooterFromState(getState().footer);
+ }
+
+ // Sorting
+ if (stateChangeEvent.hasPropertyChanged("sortColumns")
+ || stateChangeEvent.hasPropertyChanged("sortDirs")) {
+ onSortStateChange();
+ }
+
+ // Editor
+ if (stateChangeEvent.hasPropertyChanged("editorEnabled")) {
+ getWidget().setEditorEnabled(getState().editorEnabled);
+ }
+
+ // Frozen columns
+ if (stateChangeEvent.hasPropertyChanged("frozenColumnCount")) {
+ getWidget().setFrozenColumnCount(getState().frozenColumnCount);
+ }
+
+ // Theme features
+ String activeTheme = getConnection().getUIConnector().getActiveTheme();
+ if (lastKnownTheme == null) {
+ lastKnownTheme = activeTheme;
+ } else if (!lastKnownTheme.equals(activeTheme)) {
+ getWidget().resetSizesFromDom();
+ lastKnownTheme = activeTheme;
+ }
+ }
+
+ private void updateColumnOrderFromState(List<String> stateColumnOrder) {
+ CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder
+ .size()];
+ int i = 0;
+ for (String id : stateColumnOrder) {
+ columns[i] = columnIdToColumn.get(id);
+ i++;
+ }
+ columnsUpdatedFromState = true;
+ getWidget().setColumnOrder(columns);
+ columnsUpdatedFromState = false;
+ columnOrder = stateColumnOrder;
+ }
+
+ private boolean orderNeedsUpdate(List<String> stateColumnOrder) {
+ if (stateColumnOrder.size() == columnOrder.size()) {
+ for (int i = 0; i < columnOrder.size(); ++i) {
+ if (!stateColumnOrder.get(i).equals(columnOrder.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private void updateHeaderFromState(GridStaticSectionState state) {
+ getWidget().setHeaderVisible(state.visible);
+
+ while (getWidget().getHeaderRowCount() > 0) {
+ getWidget().removeHeaderRow(0);
+ }
+
+ for (RowState rowState : state.rows) {
+ HeaderRow row = getWidget().appendHeaderRow();
+
+ if (rowState.defaultRow) {
+ getWidget().setDefaultHeaderRow(row);
+ }
+
+ for (CellState cellState : rowState.cells) {
+ CustomGridColumn column = columnIdToColumn
+ .get(cellState.columnId);
+ updateHeaderCellFromState(row.getCell(column), cellState);
+ }
+
+ for (Set<String> group : rowState.cellGroups.keySet()) {
+ Grid.Column<?, ?>[] columns = new Grid.Column<?, ?>[group
+ .size()];
+ CellState cellState = rowState.cellGroups.get(group);
+
+ int i = 0;
+ for (String columnId : group) {
+ columns[i] = columnIdToColumn.get(columnId);
+ i++;
+ }
+
+ // Set state to be the same as first in group.
+ updateHeaderCellFromState(row.join(columns), cellState);
+ }
+
+ row.setStyleName(rowState.styleName);
+ }
+ }
+
+ private void updateHeaderCellFromState(HeaderCell cell,
+ CellState cellState) {
+ switch (cellState.type) {
+ case TEXT:
+ cell.setText(cellState.text);
+ break;
+ case HTML:
+ cell.setHtml(cellState.html);
+ break;
+ case WIDGET:
+ ComponentConnector connector = (ComponentConnector) cellState.connector;
+ if (connector != null) {
+ cell.setWidget(connector.getWidget());
+ } else {
+ // This happens if you do setVisible(false) on the component on
+ // the server side
+ cell.setWidget(null);
+ }
+ break;
+ default:
+ throw new IllegalStateException(
+ "unexpected cell type: " + cellState.type);
+ }
+ cell.setStyleName(cellState.styleName);
+ }
+
+ private void updateFooterFromState(GridStaticSectionState state) {
+ getWidget().setFooterVisible(state.visible);
+
+ while (getWidget().getFooterRowCount() > 0) {
+ getWidget().removeFooterRow(0);
+ }
+
+ for (RowState rowState : state.rows) {
+ FooterRow row = getWidget().appendFooterRow();
+
+ for (CellState cellState : rowState.cells) {
+ CustomGridColumn column = columnIdToColumn
+ .get(cellState.columnId);
+ updateFooterCellFromState(row.getCell(column), cellState);
+ }
+
+ for (Set<String> group : rowState.cellGroups.keySet()) {
+ Grid.Column<?, ?>[] columns = new Grid.Column<?, ?>[group
+ .size()];
+ CellState cellState = rowState.cellGroups.get(group);
+
+ int i = 0;
+ for (String columnId : group) {
+ columns[i] = columnIdToColumn.get(columnId);
+ i++;
+ }
+
+ // Set state to be the same as first in group.
+ updateFooterCellFromState(row.join(columns), cellState);
+ }
+
+ row.setStyleName(rowState.styleName);
+ }
+ }
+
+ private void updateFooterCellFromState(FooterCell cell,
+ CellState cellState) {
+ switch (cellState.type) {
+ case TEXT:
+ cell.setText(cellState.text);
+ break;
+ case HTML:
+ cell.setHtml(cellState.html);
+ break;
+ case WIDGET:
+ ComponentConnector connector = (ComponentConnector) cellState.connector;
+ if (connector != null) {
+ cell.setWidget(connector.getWidget());
+ } else {
+ // This happens if you do setVisible(false) on the component on
+ // the server side
+ cell.setWidget(null);
+ }
+ break;
+ default:
+ throw new IllegalStateException(
+ "unexpected cell type: " + cellState.type);
+ }
+ cell.setStyleName(cellState.styleName);
+ }
+
+ /**
+ * Updates a column from a state change event.
+ *
+ * @param columnIndex
+ * The index of the column to update
+ */
+ private void updateColumnFromStateChangeEvent(GridColumnState columnState) {
+ CustomGridColumn column = columnIdToColumn.get(columnState.id);
+
+ columnsUpdatedFromState = true;
+ updateColumnFromState(column, columnState);
+ columnsUpdatedFromState = false;
+ }
+
+ /**
+ * Adds a new column to the grid widget from a state change event
+ *
+ * @param columnIndex
+ * The index of the column, according to how it
+ */
+ private void addColumnFromStateChangeEvent(GridColumnState state) {
+ @SuppressWarnings("unchecked")
+ CustomGridColumn column = new CustomGridColumn(state.id,
+ ((AbstractGridRendererConnector<Object>) state.rendererConnector));
+ columnIdToColumn.put(state.id, column);
+
+ /*
+ * Add column to grid. Reordering is handled as a separate problem.
+ */
+ getWidget().addColumn(column);
+ columnOrder.add(state.id);
+ }
+
+ /**
+ * Updates the column values from a state
+ *
+ * @param column
+ * The column to update
+ * @param state
+ * The state to get the data from
+ */
+ @SuppressWarnings("unchecked")
+ private static void updateColumnFromState(CustomGridColumn column,
+ GridColumnState state) {
+ column.setWidth(state.width);
+ column.setMinimumWidth(state.minWidth);
+ column.setMaximumWidth(state.maxWidth);
+ column.setExpandRatio(state.expandRatio);
+
+ assert state.rendererConnector instanceof AbstractGridRendererConnector : "GridColumnState.rendererConnector is invalid (not subclass of AbstractGridRendererConnector)";
+ column.setRenderer(
+ (AbstractGridRendererConnector<Object>) state.rendererConnector);
+
+ column.setSortable(state.sortable);
+
+ column.setResizable(state.resizable);
+
+ column.setHeaderCaption(state.headerCaption);
+
+ column.setHidden(state.hidden);
+ column.setHidable(state.hidable);
+ column.setHidingToggleCaption(state.hidingToggleCaption);
+
+ column.setEditable(state.editable);
+ column.setEditorConnector(
+ (AbstractComponentConnector) state.editorConnector);
+ }
+
+ /**
+ * Removes any orphan columns that has been removed from the state from the
+ * grid
+ */
+ private void purgeRemovedColumns() {
+
+ // Get columns still registered in the state
+ Set<String> columnsInState = new HashSet<String>();
+ for (GridColumnState columnState : getState().columns) {
+ columnsInState.add(columnState.id);
+ }
+
+ // Remove column no longer in state
+ Iterator<String> columnIdIterator = columnIdToColumn.keySet()
+ .iterator();
+ while (columnIdIterator.hasNext()) {
+ String id = columnIdIterator.next();
+ if (!columnsInState.contains(id)) {
+ CustomGridColumn column = columnIdToColumn.get(id);
+ columnIdIterator.remove();
+ getWidget().removeColumn(column);
+ columnOrder.remove(id);
+ }
+ }
+ }
+
+ public void setDataSource(RpcDataSource dataSource) {
+ this.dataSource = dataSource;
+ getWidget().setDataSource(this.dataSource);
+ }
+
+ private void onSortStateChange() {
+ List<SortOrder> sortOrder = new ArrayList<SortOrder>();
+
+ String[] sortColumns = getState().sortColumns;
+ SortDirection[] sortDirs = getState().sortDirs;
+
+ for (int i = 0; i < sortColumns.length; i++) {
+ sortOrder.add(new SortOrder(columnIdToColumn.get(sortColumns[i]),
+ sortDirs[i]));
+ }
+
+ getWidget().setSortOrder(sortOrder);
+ }
+
+ private Logger getLogger() {
+ return Logger.getLogger(getClass().getName());
+ }
+
+ /**
+ * Gets the row key for a row object.
+ *
+ * @param row
+ * the row object
+ * @return the key for the given row
+ */
+ public String getRowKey(JsonObject row) {
+ final Object key = dataSource.getRowKey(row);
+ assert key instanceof String : "Internal key was not a String but a "
+ + key.getClass().getSimpleName() + " (" + key + ")";
+ return (String) key;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin.client
+ * .ComponentConnector)
+ */
+ @Override
+ public void updateCaption(ComponentConnector connector) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void onConnectorHierarchyChange(
+ ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
+ customDetailsGenerator.updateConnectorHierarchy(getChildren());
+ }
+
+ public String getColumnId(Grid.Column<?, ?> column) {
+ if (column instanceof CustomGridColumn) {
+ return ((CustomGridColumn) column).id;
+ }
+ return null;
+ }
+
+ @Override
+ public void layout() {
+ getWidget().onResize();
+ }
+
+ @Override
+ public boolean isWorkPending() {
+ return lazyDetailsScroller.isWorkPending();
+ }
+
+ /**
+ * Gets the listener used by this connector for tracking when row detail
+ * visibility changes.
+ *
+ * @since 7.5.0
+ * @return the used details listener
+ */
+ 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);
+ }
+
+ @Override
+ protected void sendContextClickEvent(MouseEventDetails details,
+ EventTarget eventTarget) {
+ // if element is the resize indicator, ignore the event
+ if (isResizeHandle(eventTarget)) {
+ WidgetUtil.clearTextSelection();
+ return;
+ }
+
+ EventCellReference<JsonObject> eventCell = getWidget().getEventCell();
+
+ Section section = eventCell.getSection();
+ String rowKey = null;
+ if (eventCell.isBody() && eventCell.getRow() != null) {
+ rowKey = getRowKey(eventCell.getRow());
+ }
+
+ String columnId = getColumnId(eventCell.getColumn());
+
+ getRpcProxy(GridServerRpc.class).contextClick(eventCell.getRowIndex(),
+ rowKey, columnId, section, details);
+
+ WidgetUtil.clearTextSelection();
+ }
+
+ private boolean isResizeHandle(EventTarget eventTarget) {
+ if (Element.is(eventTarget)) {
+ Element e = Element.as(eventTarget);
+ if (e.getClassName().contains("-column-resize-handle")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates a concatenation of all columns errors for Editor.
+ *
+ * @since 7.6
+ * @return displayed error string
+ */
+ private String getColumnErrors() {
+ List<String> errors = new ArrayList<String>();
+
+ for (Grid.Column<?, JsonObject> c : getWidget().getColumns()) {
+ if (!(c instanceof CustomGridColumn)) {
+ continue;
+ }
+
+ String error = columnToErrorMessage.get(c);
+ if (error != null) {
+ errors.add(error);
+ }
+ }
+
+ String result = "";
+ Iterator<String> i = errors.iterator();
+ while (i.hasNext()) {
+ result += i.next();
+ if (i.hasNext()) {
+ result += ", ";
+ }
+ }
+ return result.isEmpty() ? null : result;
+ }
+
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/ImageRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/ImageRendererConnector.java
new file mode 100644
index 0000000000..7949eeab3c
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/ImageRendererConnector.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.vaadin.client.communication.JsonDecoder;
+import com.vaadin.client.metadata.TypeDataStore;
+import com.vaadin.client.renderers.ClickableRenderer.RendererClickHandler;
+import com.vaadin.client.renderers.ImageRenderer;
+import com.vaadin.shared.communication.URLReference;
+import com.vaadin.shared.ui.Connect;
+
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+/**
+ * A connector for {@link ImageRenderer}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.ImageRenderer.class)
+public class ImageRendererConnector extends ClickableRendererConnector<String> {
+
+ @Override
+ public ImageRenderer getRenderer() {
+ return (ImageRenderer) super.getRenderer();
+ }
+
+ @Override
+ public String decode(JsonValue value) {
+ URLReference reference = (URLReference) JsonDecoder.decodeValue(
+ TypeDataStore.getType(URLReference.class), value, null,
+ getConnection());
+
+ return reference != null ? reference.getURL() : null;
+ }
+
+ @Override
+ protected HandlerRegistration addClickHandler(
+ RendererClickHandler<JsonObject> handler) {
+ return getRenderer().addClickHandler(handler);
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/JavaScriptRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/JavaScriptRendererConnector.java
new file mode 100644
index 0000000000..1515f1aead
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/JavaScriptRendererConnector.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.NativeEvent;
+import com.vaadin.client.JavaScriptConnectorHelper;
+import com.vaadin.client.Util;
+import com.vaadin.client.communication.HasJavaScriptConnectorHelper;
+import com.vaadin.client.renderers.ComplexRenderer;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.CellReference;
+import com.vaadin.client.widget.grid.RendererCellReference;
+import com.vaadin.shared.JavaScriptExtensionState;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.ui.renderers.AbstractJavaScriptRenderer;
+
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+/**
+ * Connector for server-side renderer implemented using JavaScript.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+// This is really typed to <JsonValue>, but because of the way native strings
+// are not always instanceof JsonValue, we need to accept Object
+@Connect(AbstractJavaScriptRenderer.class)
+public class JavaScriptRendererConnector
+ extends AbstractGridRendererConnector<Object>
+ implements HasJavaScriptConnectorHelper {
+ private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper(
+ this);
+
+ private final JavaScriptObject cellReferenceWrapper = createCellReferenceWrapper();
+
+ @Override
+ protected void init() {
+ super.init();
+ helper.init();
+
+ addGetRowKey(helper.getConnectorWrapper());
+ }
+
+ private static native JavaScriptObject createCellReferenceWrapper()
+ /*-{
+ var reference = {};
+
+ var setProperty = function(name, getter, setter) {
+ var descriptor = {
+ get: getter
+ }
+ if (setter) {
+ descriptor.set = setter;
+ }
+ Object.defineProperty(reference, name, descriptor);
+ };
+
+ setProperty("element", function() {
+ return reference.target.@CellReference::getElement()();
+ }, null);
+
+ setProperty("rowIndex", function() {
+ return reference.target.@CellReference::getRowIndex()();
+ }, null);
+
+ setProperty("columnIndex", function() {
+ return reference.target.@CellReference::getColumnIndex()();
+ }, null);
+
+ setProperty("colSpan", function() {
+ return reference.target.@RendererCellReference::getColSpan()();
+ }, function(colSpan) {
+ reference.target.@RendererCellReference::setColSpan(*)(colSpan);
+ });
+
+ return reference;
+ }-*/;
+
+ @Override
+ public JavaScriptExtensionState getState() {
+ return (JavaScriptExtensionState) super.getState();
+ }
+
+ private native void addGetRowKey(JavaScriptObject wrapper)
+ /*-{
+ var self = this;
+ wrapper.getRowKey = $entry(function(rowIndex) {
+ return @JavaScriptRendererConnector::findRowKey(*)(self, rowIndex);
+ });
+ }-*/;
+
+ private static String findRowKey(JavaScriptRendererConnector connector,
+ int rowIndex) {
+ GridConnector gc = (GridConnector) connector.getParent();
+ JsonObject row = gc.getWidget().getDataSource().getRow(rowIndex);
+ return connector.getRowKey(row);
+ }
+
+ private boolean hasFunction(String name) {
+ return hasFunction(helper.getConnectorWrapper(), name);
+ }
+
+ private static native boolean hasFunction(JavaScriptObject wrapper,
+ String name)
+ /*-{
+ return typeof wrapper[name] === 'function';
+ }-*/;
+
+ @Override
+ protected Renderer<Object> createRenderer() {
+ helper.ensureJavascriptInited();
+
+ if (!hasFunction("render")) {
+ throw new RuntimeException(
+ "JavaScriptRenderer " + helper.getInitFunctionName()
+ + " must have a function named 'render'");
+ }
+
+ final boolean hasInit = hasFunction("init");
+ final boolean hasDestroy = hasFunction("destroy");
+ final boolean hasOnActivate = hasFunction("onActivate");
+ final boolean hasGetConsumedEvents = hasFunction("getConsumedEvents");
+ final boolean hasOnBrowserEvent = hasFunction("onBrowserEvent");
+
+ return new ComplexRenderer<Object>() {
+ @Override
+ public void render(RendererCellReference cell, Object data) {
+ if (data instanceof JsonValue) {
+ data = Util.json2jso((JsonValue) data);
+ }
+ render(helper.getConnectorWrapper(), getJsCell(cell), data);
+ }
+
+ private JavaScriptObject getJsCell(CellReference<?> cell) {
+ updateCellReference(cellReferenceWrapper, cell);
+ return cellReferenceWrapper;
+ }
+
+ public native void render(JavaScriptObject wrapper,
+ JavaScriptObject cell, Object data)
+ /*-{
+ wrapper.render(cell, data);
+ }-*/;
+
+ @Override
+ public void init(RendererCellReference cell) {
+ if (hasInit) {
+ init(helper.getConnectorWrapper(), getJsCell(cell));
+ }
+ }
+
+ private native void init(JavaScriptObject wrapper,
+ JavaScriptObject cell)
+ /*-{
+ wrapper.init(cell);
+ }-*/;
+
+ private native void updateCellReference(
+ JavaScriptObject cellWrapper, CellReference<?> target)
+ /*-{
+ cellWrapper.target = target;
+ }-*/;
+
+ @Override
+ public void destroy(RendererCellReference cell) {
+ if (hasDestroy) {
+ destory(helper.getConnectorWrapper(), getJsCell(cell));
+ } else {
+ super.destroy(cell);
+ }
+ }
+
+ private native void destory(JavaScriptObject wrapper,
+ JavaScriptObject cell)
+ /*-{
+ wrapper.destory(cell);
+ }-*/;
+
+ @Override
+ public boolean onActivate(CellReference<?> cell) {
+ if (hasOnActivate) {
+ return onActivate(helper.getConnectorWrapper(),
+ getJsCell(cell));
+ } else {
+ return super.onActivate(cell);
+ }
+ }
+
+ private native boolean onActivate(JavaScriptObject wrapper,
+ JavaScriptObject cell)
+ /*-{
+ return !!wrapper.onActivate(cell);
+ }-*/;
+
+ @Override
+ public Collection<String> getConsumedEvents() {
+ if (hasGetConsumedEvents) {
+ JsArrayString events = getConsumedEvents(
+ helper.getConnectorWrapper());
+
+ ArrayList<String> list = new ArrayList<String>(
+ events.length());
+ for (int i = 0; i < events.length(); i++) {
+ list.add(events.get(i));
+ }
+ return list;
+ } else {
+ return super.getConsumedEvents();
+ }
+ }
+
+ private native JsArrayString getConsumedEvents(
+ JavaScriptObject wrapper)
+ /*-{
+ var rawEvents = wrapper.getConsumedEvents();
+ var events = [];
+ for(var i = 0; i < rawEvents.length; i++) {
+ events[i] = ""+rawEvents[i];
+ }
+ return events;
+ }-*/;
+
+ @Override
+ public boolean onBrowserEvent(CellReference<?> cell,
+ NativeEvent event) {
+ if (hasOnBrowserEvent) {
+ return onBrowserEvent(helper.getConnectorWrapper(),
+ getJsCell(cell), event);
+ } else {
+ return super.onBrowserEvent(cell, event);
+ }
+ }
+
+ private native boolean onBrowserEvent(JavaScriptObject wrapper,
+ JavaScriptObject cell, NativeEvent event)
+ /*-{
+ return !!wrapper.onBrowserEvent(cell, event);
+ }-*/;
+ };
+ }
+
+ @Override
+ public Object decode(JsonValue value) {
+ // Let the js logic decode the raw json that the server sent
+ return value;
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+ helper.onUnregister();
+ }
+
+ @Override
+ public JavaScriptConnectorHelper getJavascriptConnectorHelper() {
+ return helper;
+ }
+}
+
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java
new file mode 100644
index 0000000000..e7494737cb
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.data.DataSource;
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.renderers.ComplexRenderer;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.DataAvailableEvent;
+import com.vaadin.client.widget.grid.DataAvailableHandler;
+import com.vaadin.client.widget.grid.events.SelectAllEvent;
+import com.vaadin.client.widget.grid.events.SelectAllHandler;
+import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.HeaderCell;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.Range;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
+import com.vaadin.ui.LegacyGrid.MultiSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link MultiSelectionModel}.
+ *
+ * @since 7.6
+ * @author Vaadin Ltd
+ */
+@Connect(MultiSelectionModel.class)
+public class MultiSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel.Multi<JsonObject>> {
+
+ private Multi<JsonObject> selectionModel = createSelectionModel();
+ private SpaceSelectHandler<JsonObject> spaceHandler;
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(selectionModel);
+ spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public void onUnregister() {
+ spaceHandler.removeHandler();
+ }
+
+ @Override
+ protected Multi<JsonObject> createSelectionModel() {
+ return new MultiSelectionModel();
+ }
+
+ @Override
+ public MultiSelectionModelState getState() {
+ return (MultiSelectionModelState) super.getState();
+ }
+
+ @OnStateChange("allSelected")
+ void updateSelectAllCheckbox() {
+ if (selectionModel.getSelectionColumnRenderer() != null) {
+ HeaderCell cell = getGrid().getDefaultHeaderRow()
+ .getCell(getGrid().getColumn(0));
+ CheckBox widget = (CheckBox) cell.getWidget();
+ widget.setValue(getState().allSelected, false);
+ }
+ }
+
+ protected class MultiSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.Multi.Batched<JsonObject> {
+
+ private ComplexRenderer<Boolean> renderer = null;
+ private Set<RowHandle<JsonObject>> selected = new HashSet<RowHandle<JsonObject>>();
+ private Set<RowHandle<JsonObject>> deselected = new HashSet<RowHandle<JsonObject>>();
+ private HandlerRegistration selectAll;
+ private HandlerRegistration dataAvailable;
+ private Range availableRows;
+ private boolean batchSelect = false;
+
+ @Override
+ public void setGrid(Grid<JsonObject> grid) {
+ super.setGrid(grid);
+ if (grid != null) {
+ renderer = createSelectionColumnRenderer(grid);
+ selectAll = getGrid().addSelectAllHandler(
+ new SelectAllHandler<JsonObject>() {
+
+ @Override
+ public void onSelectAll(
+ SelectAllEvent<JsonObject> event) {
+ selectAll();
+ }
+ });
+ dataAvailable = getGrid()
+ .addDataAvailableHandler(new DataAvailableHandler() {
+
+ @Override
+ public void onDataAvailable(
+ DataAvailableEvent event) {
+ availableRows = event.getAvailableRows();
+ }
+ });
+ } else if (renderer != null) {
+ selectAll.removeHandler();
+ dataAvailable.removeHandler();
+ renderer = null;
+ }
+ }
+
+ /**
+ * Creates a selection column renderer. This method can be overridden to
+ * use a custom renderer or use {@code null} to disable the selection
+ * column.
+ *
+ * @param grid
+ * the grid for this selection model
+ * @return selection column renderer or {@code null} if not needed
+ */
+ protected ComplexRenderer<Boolean> createSelectionColumnRenderer(
+ Grid<JsonObject> grid) {
+ return new MultiSelectionRenderer<JsonObject>(grid);
+ }
+
+ /**
+ * Selects all available rows, sends request to server to select
+ * everything.
+ */
+ public void selectAll() {
+ assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection.";
+
+ DataSource<JsonObject> dataSource = getGrid().getDataSource();
+ for (int i = availableRows.getStart(); i < availableRows
+ .getEnd(); ++i) {
+ final JsonObject row = dataSource.getRow(i);
+ if (row != null) {
+ RowHandle<JsonObject> handle = dataSource.getHandle(row);
+ markAsSelected(handle, true);
+ }
+ }
+
+ getRpcProxy(MultiSelectionModelServerRpc.class).selectAll();
+ }
+
+ @Override
+ public Renderer<Boolean> getSelectionColumnRenderer() {
+ return renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean select(JsonObject... rows) {
+ return select(Arrays.asList(rows));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean deselect(JsonObject... rows) {
+ return deselect(Arrays.asList(rows));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always {@code true}
+ */
+ @Override
+ public boolean deselectAll() {
+ assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection.";
+
+ DataSource<JsonObject> dataSource = getGrid().getDataSource();
+ for (int i = availableRows.getStart(); i < availableRows
+ .getEnd(); ++i) {
+ final JsonObject row = dataSource.getRow(i);
+ if (row != null) {
+ RowHandle<JsonObject> handle = dataSource.getHandle(row);
+ markAsSelected(handle, false);
+ }
+ }
+
+ getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll();
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean select(Collection<JsonObject> rows) {
+ if (rows.isEmpty()) {
+ return false;
+ }
+
+ for (JsonObject row : rows) {
+ RowHandle<JsonObject> rowHandle = getRowHandle(row);
+ if (markAsSelected(rowHandle, true)) {
+ selected.add(rowHandle);
+ }
+ }
+
+ if (!isBeingBatchSelected()) {
+ sendSelected();
+ }
+ return true;
+ }
+
+ /**
+ * Marks the given row to be selected or deselected. Returns true if the
+ * value actually changed.
+ * <p>
+ * Note: If selection model is in batch select state, the row will be
+ * pinned on select.
+ *
+ * @param row
+ * row handle
+ * @param selected
+ * {@code true} if row should be selected; {@code false} if
+ * not
+ * @return {@code true} if selected status changed; {@code false} if not
+ */
+ protected boolean markAsSelected(RowHandle<JsonObject> row,
+ boolean selected) {
+ if (selected && !isSelected(row.getRow())) {
+ row.getRow().put(GridState.JSONKEY_SELECTED, true);
+ } else if (!selected && isSelected(row.getRow())) {
+ row.getRow().remove(GridState.JSONKEY_SELECTED);
+ } else {
+ return false;
+ }
+
+ row.updateRow();
+
+ if (isBeingBatchSelected()) {
+ row.pin();
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean deselect(Collection<JsonObject> rows) {
+ if (rows.isEmpty()) {
+ return false;
+ }
+
+ for (JsonObject row : rows) {
+ RowHandle<JsonObject> rowHandle = getRowHandle(row);
+ if (markAsSelected(rowHandle, false)) {
+ deselected.add(rowHandle);
+ }
+ }
+
+ if (!isBeingBatchSelected()) {
+ sendDeselected();
+ }
+ return true;
+ }
+
+ /**
+ * Sends a deselect RPC call to server-side containing all deselected
+ * rows. Unpins any pinned rows.
+ */
+ private void sendDeselected() {
+ getRpcProxy(MultiSelectionModelServerRpc.class)
+ .deselect(getRowKeys(deselected));
+
+ if (isBeingBatchSelected()) {
+ for (RowHandle<JsonObject> row : deselected) {
+ row.unpin();
+ }
+ }
+
+ deselected.clear();
+ }
+
+ /**
+ * Sends a select RPC call to server-side containing all selected rows.
+ * Unpins any pinned rows.
+ */
+ private void sendSelected() {
+ getRpcProxy(MultiSelectionModelServerRpc.class)
+ .select(getRowKeys(selected));
+
+ if (isBeingBatchSelected()) {
+ for (RowHandle<JsonObject> row : selected) {
+ row.unpin();
+ }
+ }
+
+ selected.clear();
+ }
+
+ private List<String> getRowKeys(Set<RowHandle<JsonObject>> handles) {
+ List<String> keys = new ArrayList<String>();
+ for (RowHandle<JsonObject> handle : handles) {
+ keys.add(getRowKey(handle.getRow()));
+ }
+ return keys;
+ }
+
+ private Set<JsonObject> getRows(Set<RowHandle<JsonObject>> handles) {
+ Set<JsonObject> rows = new HashSet<JsonObject>();
+ for (RowHandle<JsonObject> handle : handles) {
+ rows.add(handle.getRow());
+ }
+ return rows;
+ }
+
+ @Override
+ public void startBatchSelect() {
+ assert selected.isEmpty()
+ && deselected.isEmpty() : "Row caches were not clear.";
+ batchSelect = true;
+ }
+
+ @Override
+ public void commitBatchSelect() {
+ assert batchSelect : "Not batch selecting.";
+ if (!selected.isEmpty()) {
+ sendSelected();
+ }
+
+ if (!deselected.isEmpty()) {
+ sendDeselected();
+ }
+ batchSelect = false;
+ }
+
+ @Override
+ public boolean isBeingBatchSelected() {
+ return batchSelect;
+ }
+
+ @Override
+ public Collection<JsonObject> getSelectedRowsBatch() {
+ return Collections.unmodifiableSet(getRows(selected));
+ }
+
+ @Override
+ public Collection<JsonObject> getDeselectedRowsBatch() {
+ return Collections.unmodifiableSet(getRows(deselected));
+ }
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/NoSelectionModelConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/NoSelectionModelConnector.java
new file mode 100644
index 0000000000..1a080f5082
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/NoSelectionModelConnector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModelNone;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.ui.LegacyGrid.NoSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link NoSelectionModel}.
+ *
+ * @since 7.6
+ * @author Vaadin Ltd
+ */
+@Connect(NoSelectionModel.class)
+public class NoSelectionModelConnector
+ extends AbstractSelectionModelConnector<SelectionModel<JsonObject>> {
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(createSelectionModel());
+ }
+
+ @Override
+ protected SelectionModel<JsonObject> createSelectionModel() {
+ return new SelectionModelNone<JsonObject>();
+ }
+} \ No newline at end of file
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/NumberRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/NumberRendererConnector.java
new file mode 100644
index 0000000000..ff16047b0d
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/NumberRendererConnector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * A connector for {@link com.vaadin.ui.components.grid.renderers.NumberRenderer
+ * NumberRenderer} .
+ * <p>
+ * The server-side Renderer operates on numbers, but the data is serialized as a
+ * string, and displayed as-is on the client side. This is to be able to support
+ * the server's locale.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.NumberRenderer.class)
+public class NumberRendererConnector extends TextRendererConnector {
+ // no implementation needed
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/ProgressBarRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/ProgressBarRendererConnector.java
new file mode 100644
index 0000000000..5687d031aa
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/ProgressBarRendererConnector.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.renderers.ProgressBarRenderer;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * A connector for {@link ProgressBarRenderer}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.ProgressBarRenderer.class)
+public class ProgressBarRendererConnector
+ extends AbstractGridRendererConnector<Double> {
+
+ @Override
+ public ProgressBarRenderer getRenderer() {
+ return (ProgressBarRenderer) super.getRenderer();
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/RpcDataSourceConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/RpcDataSourceConnector.java
new file mode 100644
index 0000000000..52fa8a2e48
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/RpcDataSourceConnector.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.connectors;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.data.AbstractRemoteDataSource;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.shared.data.DataProviderRpc;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.Range;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+
+/**
+ * Connects a Vaadin server-side container data source to a Grid. This is
+ * currently implemented as an Extension hardcoded to support a specific
+ * connector type. This will be changed once framework support for something
+ * more flexible has been implemented.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.server.communication.data.RpcDataProviderExtension.class)
+public class RpcDataSourceConnector extends AbstractExtensionConnector {
+
+ /**
+ * A callback interface to let {@link GridConnector} know that detail
+ * visibilities might have changed.
+ *
+ * @since 7.5.0
+ * @author Vaadin Ltd
+ */
+ interface DetailsListener {
+
+ /**
+ * A request to verify (and correct) the visibility for a row, given
+ * updated metadata.
+ *
+ * @param rowIndex
+ * the index of the row that should be checked
+ * @param row
+ * the row object to check visibility for
+ * @see GridState#JSONKEY_DETAILS_VISIBLE
+ */
+ void reapplyDetailsVisibility(int rowIndex, JsonObject row);
+ }
+
+ public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> {
+
+ protected RpcDataSource() {
+ registerRpc(DataProviderRpc.class, new DataProviderRpc() {
+ @Override
+ public void setRowData(int firstRow, JsonArray rowArray) {
+ ArrayList<JsonObject> rows = new ArrayList<JsonObject>(
+ rowArray.length());
+ for (int i = 0; i < rowArray.length(); i++) {
+ JsonObject rowObject = rowArray.getObject(i);
+ rows.add(rowObject);
+ }
+
+ RpcDataSource.this.setRowData(firstRow, rows);
+ }
+
+ @Override
+ public void removeRowData(int firstRow, int count) {
+ RpcDataSource.this.removeRowData(firstRow, count);
+ }
+
+ @Override
+ public void insertRowData(int firstRow, int count) {
+ RpcDataSource.this.insertRowData(firstRow, count);
+ }
+
+ @Override
+ public void resetDataAndSize(int size) {
+ RpcDataSource.this.resetDataAndSize(size);
+ }
+
+ @Override
+ public void updateRowData(JsonArray rowArray) {
+ for (int i = 0; i < rowArray.length(); ++i) {
+ RpcDataSource.this.updateRowData(rowArray.getObject(i));
+ }
+ }
+ });
+ }
+
+ private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class);
+ private DetailsListener detailsListener;
+ private JsonArray droppedRowKeys = Json.createArray();
+
+ @Override
+ protected void requestRows(int firstRowIndex, int numberOfRows,
+ RequestRowsCallback<JsonObject> callback) {
+ if (droppedRowKeys.length() > 0) {
+ rpcProxy.dropRows(droppedRowKeys);
+ droppedRowKeys = Json.createArray();
+ }
+
+ /*
+ * If you're looking at this code because you want to learn how to
+ * use AbstactRemoteDataSource, please look somewhere else instead.
+ *
+ * We're not doing things in the conventional way with the callback
+ * here since Vaadin doesn't directly support RPC with return
+ * values. We're instead asking the server to push us some data, and
+ * when we receive pushed data, we just push it along to the
+ * underlying cache in the same way no matter if it was a genuine
+ * push or just a result of us requesting rows.
+ */
+
+ Range cached = getCachedRange();
+
+ rpcProxy.requestRows(firstRowIndex, numberOfRows, cached.getStart(),
+ cached.length());
+
+ /*
+ * Show the progress indicator if there is a pending data request
+ * and some of the visible rows are being requested. The RPC in
+ * itself will not trigger the indicator since it might just fetch
+ * some rows in the background to fill the cache.
+ *
+ * The indicator will be hidden by the framework when the response
+ * is received (unless another request is already on its way at that
+ * point).
+ */
+ if (getRequestedAvailability().intersects(
+ Range.withLength(firstRowIndex, numberOfRows))) {
+ getConnection().getLoadingIndicator().ensureTriggered();
+ }
+ }
+
+ @Override
+ public void ensureAvailability(int firstRowIndex, int numberOfRows) {
+ super.ensureAvailability(firstRowIndex, numberOfRows);
+
+ /*
+ * We trigger the indicator already at this point since the actual
+ * RPC will not be sent right away when waiting for the response to
+ * a previous request.
+ *
+ * Only triggering here would not be enough since the check that
+ * sets isWaitingForData is deferred. We don't want to trigger the
+ * loading indicator here if we don't know that there is actually a
+ * request going on since some other bug might then cause the
+ * loading indicator to not be hidden.
+ */
+ if (isWaitingForData()
+ && !Range.withLength(firstRowIndex, numberOfRows)
+ .isSubsetOf(getCachedRange())) {
+ getConnection().getLoadingIndicator().ensureTriggered();
+ }
+ }
+
+ @Override
+ public String getRowKey(JsonObject row) {
+ if (row.hasKey(GridState.JSONKEY_ROWKEY)) {
+ return row.getString(GridState.JSONKEY_ROWKEY);
+ } else {
+ return null;
+ }
+ }
+
+ public RowHandle<JsonObject> getHandleByKey(Object key) {
+ JsonObject row = Json.createObject();
+ row.put(GridState.JSONKEY_ROWKEY, (String) key);
+ return new RowHandleImpl(row, key);
+ }
+
+ @Override
+ protected void unpinHandle(RowHandleImpl handle) {
+ // Row data is no longer available after it has been unpinned.
+ String key = getRowKey(handle.getRow());
+ super.unpinHandle(handle);
+ if (!handle.isPinned()) {
+ if (indexOfKey(key) == -1) {
+ // Row out of view has been unpinned. drop it
+ droppedRowKeys.set(droppedRowKeys.length(), key);
+ }
+ }
+ }
+
+ void setDetailsListener(DetailsListener detailsListener) {
+ this.detailsListener = detailsListener;
+ }
+
+ @Override
+ protected void setRowData(int firstRowIndex, List<JsonObject> rowData) {
+ super.setRowData(firstRowIndex, rowData);
+
+ /*
+ * Intercepting details information from the data source, rerouting
+ * them back to the GridConnector (as a details listener)
+ */
+ for (int i = 0; i < rowData.size(); i++) {
+ detailsListener.reapplyDetailsVisibility(firstRowIndex + i,
+ rowData.get(i));
+ }
+ }
+
+ /**
+ * Updates row data based on row key.
+ *
+ * @since 7.6
+ * @param row
+ * new row object
+ */
+ protected void updateRowData(JsonObject row) {
+ int index = indexOfKey(getRowKey(row));
+ if (index >= 0) {
+ setRowData(index, Collections.singletonList(row));
+ }
+ }
+
+ @Override
+ protected void onDropFromCache(int rowIndex, JsonObject row) {
+ if (!isPinned(row)) {
+ droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row));
+ }
+ }
+ }
+
+ private final RpcDataSource dataSource = new RpcDataSource();
+
+ @Override
+ protected void extend(ServerConnector target) {
+ GridConnector gridConnector = (GridConnector) target;
+ dataSource.setDetailsListener(gridConnector.getDetailsListener());
+ gridConnector.setDataSource(dataSource);
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java
new file mode 100644
index 0000000000..980c4458d4
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Single;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
+import com.vaadin.ui.LegacyGrid.SingleSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link SingleSelectionModel}.
+ *
+ * @since 7.6
+ * @author Vaadin Ltd
+ */
+@Connect(SingleSelectionModel.class)
+public class SingleSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel.Single<JsonObject>> {
+
+ private SpaceSelectHandler<JsonObject> spaceHandler;
+ private ClickSelectHandler<JsonObject> clickHandler;
+ private Single<JsonObject> selectionModel = createSelectionModel();
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(selectionModel);
+ spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid());
+ clickHandler = new ClickSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public SingleSelectionModelState getState() {
+ return (SingleSelectionModelState) super.getState();
+ }
+
+ @Override
+ public void onUnregister() {
+ spaceHandler.removeHandler();
+ clickHandler.removeHandler();
+
+ super.onUnregister();
+ }
+
+ @Override
+ protected Single<JsonObject> createSelectionModel() {
+ return new SingleSelectionModel();
+ }
+
+ @OnStateChange("deselectAllowed")
+ void updateDeselectAllowed() {
+ selectionModel.setDeselectAllowed(getState().deselectAllowed);
+ }
+
+ /**
+ * SingleSelectionModel without a selection column renderer.
+ */
+ public class SingleSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.Single<JsonObject> {
+
+ private RowHandle<JsonObject> selectedRow;
+ private boolean deselectAllowed;
+
+ @Override
+ public Renderer<Boolean> getSelectionColumnRenderer() {
+ return null;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+
+ // Clean up selected row
+ if (selectedRow != null) {
+ clearSelectedRow();
+ }
+ }
+
+ @Override
+ public boolean select(JsonObject row) {
+ boolean changed = false;
+
+ if (row == null && !isDeselectAllowed()) {
+ // Attempting to deselect, even though it's not allowed.
+ } else {
+ if (selectedRow != null) {
+ // Check if currently re-selected row was deselected from
+ // the server.
+ if (row != null && getRowHandle(row).equals(selectedRow)) {
+ if (selectedRow.getRow()
+ .hasKey(GridState.JSONKEY_SELECTED)) {
+ // Everything is OK, no need to do anything.
+ return false;
+ }
+ }
+
+ // Remove old selected row
+ clearSelectedRow();
+ changed = true;
+ }
+
+ if (row != null) {
+ // Select the new row.
+ setSelectedRow(row);
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ getRpcProxy(SingleSelectionModelServerRpc.class)
+ .select(getRowKey(row));
+ }
+
+ return changed;
+ }
+
+ private void setSelectedRow(JsonObject row) {
+ selectedRow = getRowHandle(row);
+ selectedRow.pin();
+ selectedRow.getRow().put(GridState.JSONKEY_SELECTED, true);
+ selectedRow.updateRow();
+ }
+
+ private void clearSelectedRow() {
+ selectedRow.getRow().remove(GridState.JSONKEY_SELECTED);
+ selectedRow.updateRow();
+ selectedRow.unpin();
+ selectedRow = null;
+ }
+
+ @Override
+ public boolean deselect(JsonObject row) {
+ if (getRowHandle(row).equals(selectedRow)) {
+ select(null);
+ }
+ return false;
+ }
+
+ @Override
+ public JsonObject getSelectedRow() {
+ throw new UnsupportedOperationException(
+ "This client-side selection model "
+ + getClass().getSimpleName()
+ + " does not know selected row.");
+ }
+
+ @Override
+ public void setDeselectAllowed(boolean deselectAllowed) {
+ this.deselectAllowed = deselectAllowed;
+ }
+
+ @Override
+ public boolean isDeselectAllowed() {
+ return deselectAllowed;
+ }
+ }
+} \ No newline at end of file
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/TextRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/TextRendererConnector.java
new file mode 100644
index 0000000000..d3a289ec3e
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/TextRendererConnector.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.renderers.TextRenderer;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * A connector for {@link TextRenderer}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.TextRenderer.class)
+public class TextRendererConnector extends AbstractGridRendererConnector<String> {
+
+ @Override
+ public TextRenderer getRenderer() {
+ return (TextRenderer) super.getRenderer();
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/client/connectors/UnsafeHtmlRendererConnector.java b/compatibility-client/src/main/java/com/vaadin/client/connectors/UnsafeHtmlRendererConnector.java
new file mode 100644
index 0000000000..95c47dd242
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/client/connectors/UnsafeHtmlRendererConnector.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.RendererCellReference;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * A connector for {@link UnsafeHtmlRenderer}
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.renderers.HtmlRenderer.class)
+public class UnsafeHtmlRendererConnector
+ extends AbstractGridRendererConnector<String> {
+
+ public static class UnsafeHtmlRenderer implements Renderer<String> {
+ @Override
+ public void render(RendererCellReference cell, String data) {
+ cell.getElement().setInnerHTML(data);
+ }
+ }
+
+ @Override
+ public UnsafeHtmlRenderer getRenderer() {
+ return (UnsafeHtmlRenderer) super.getRenderer();
+ }
+}
diff --git a/compatibility-client/src/main/resources/com/vaadin/v7/Vaadin7WidgetSet.gwt.xml b/compatibility-client/src/main/resources/com/vaadin/Vaadin7WidgetSet.gwt.xml
index e124491d3a..e124491d3a 100755
--- a/compatibility-client/src/main/resources/com/vaadin/v7/Vaadin7WidgetSet.gwt.xml
+++ b/compatibility-client/src/main/resources/com/vaadin/Vaadin7WidgetSet.gwt.xml