diff options
author | Artur Signell <artur@vaadin.com> | 2016-08-18 18:04:59 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2016-08-20 00:08:44 +0300 |
commit | 6e0f2efe996cfd3b38c960e04cbced0a91215cf0 (patch) | |
tree | 9eafac7107b3d34d8d1e9bc7631a1debad4fd720 /compatibility-client | |
parent | 016a2ceb86f771a3902ace98d9fe56351c0efdf2 (diff) | |
download | vaadin-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')
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 |