From 26b06bd6361002faa9eb718fb082b4eb71e8b242 Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Tue, 12 May 2015 17:01:14 +0300 Subject: Add focus API to Grid - Server-side Grid extends AbstractFocusable - Programmatic focus, tab index, focus/blur listeners - Client-side Grid implements GWT and Vaadin Focusable - Programmatic focus, tab index, access keys Change-Id: Ic8b35ba91f82d5fba8f819897774dc89f94ecf7b --- shared/src/com/vaadin/shared/ui/grid/GridState.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'shared') diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index e039f70988..bb35659591 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -19,9 +19,9 @@ package com.vaadin.shared.ui.grid; import java.util.ArrayList; import java.util.List; -import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.annotations.DelegateToWidget; import com.vaadin.shared.data.sort.SortDirection; +import com.vaadin.shared.ui.TabIndexState; /** * The shared state for the {@link com.vaadin.ui.components.grid.Grid} component @@ -29,7 +29,7 @@ import com.vaadin.shared.data.sort.SortDirection; * @since 7.4 * @author Vaadin Ltd */ -public class GridState extends AbstractComponentState { +public class GridState extends TabIndexState { /** * A description of which of the three bundled SelectionModels is currently -- cgit v1.2.3 From fad6e73e62c53802092e824c67dd901e73683e6a Mon Sep 17 00:00:00 2001 From: patrik Date: Mon, 18 May 2015 14:36:18 +0300 Subject: Add grid editor events (#17451) Change-Id: Iebc4aece2a5be6b51289f5c2abf2d54d146621ae --- .../vaadin/client/connectors/GridConnector.java | 30 +++++ .../widget/grid/events/EditorCloseEvent.java | 34 +++++ .../client/widget/grid/events/EditorEvent.java | 108 +++++++++++++++ .../widget/grid/events/EditorEventHandler.java | 49 +++++++ .../client/widget/grid/events/EditorMoveEvent.java | 34 +++++ .../client/widget/grid/events/EditorOpenEvent.java | 34 +++++ client/src/com/vaadin/client/widgets/Grid.java | 35 ++++- server/src/com/vaadin/ui/Grid.java | 145 +++++++++++++++++++++ .../com/vaadin/shared/ui/grid/GridConstants.java | 15 +++ .../com/vaadin/shared/ui/grid/GridServerRpc.java | 25 ++++ .../grid/basicfeatures/GridBasicFeatures.java | 29 +++++ 11 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java create mode 100644 client/src/com/vaadin/client/widget/grid/events/EditorEvent.java create mode 100644 client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java create mode 100644 client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java create mode 100644 client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 47b21a3429..2542e80075 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -60,6 +60,10 @@ import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; +import com.vaadin.client.widget.grid.events.EditorCloseEvent; +import com.vaadin.client.widget.grid.events.EditorEventHandler; +import com.vaadin.client.widget.grid.events.EditorMoveEvent; +import com.vaadin.client.widget.grid.events.EditorOpenEvent; import com.vaadin.client.widget.grid.events.GridClickEvent; import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; import com.vaadin.client.widget.grid.events.SelectAllEvent; @@ -912,6 +916,32 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().setDetailsGenerator(customDetailsGenerator); getLayoutManager().registerDependency(this, getWidget().getElement()); + getWidget().addEditorEventHandler(new EditorEventHandler() { + @Override + public void onEditorOpen(EditorOpenEvent e) { + if (hasEventListener(GridConstants.EDITOR_OPEN_EVENT_ID)) { + String rowKey = getRowKey((JsonObject) e.getRow()); + getRpcProxy(GridServerRpc.class).editorOpen(rowKey); + } + } + + @Override + public void onEditorMove(EditorMoveEvent e) { + if (hasEventListener(GridConstants.EDITOR_MOVE_EVENT_ID)) { + String rowKey = getRowKey((JsonObject) e.getRow()); + getRpcProxy(GridServerRpc.class).editorMove(rowKey); + } + } + + @Override + public void onEditorClose(EditorCloseEvent e) { + if (hasEventListener(GridConstants.EDITOR_CLOSE_EVENT_ID)) { + String rowKey = getRowKey((JsonObject) e.getRow()); + getRpcProxy(GridServerRpc.class).editorClose(rowKey); + } + } + }); + layout(); } diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java new file mode 100644 index 0000000000..99f59aa82a --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.CellReference; + +/** + * Event that gets fired when an open editor is closed (and not reopened + * elsewhere) + */ +public class EditorCloseEvent extends EditorEvent { + + public EditorCloseEvent(CellReference cell) { + super(cell); + } + + @Override + protected void dispatch(EditorEventHandler handler) { + handler.onEditorClose(this); + } +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java new file mode 100644 index 0000000000..eb34033197 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.Column; + +/** + * Base class for editor events. + */ +public abstract class EditorEvent extends GwtEvent { + public static final Type TYPE = new Type(); + + private CellReference cell; + + protected EditorEvent(CellReference cell) { + this.cell = cell; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + /** + * Get a reference to the Grid that fired this Event. + * + * @return a Grid reference + */ + @SuppressWarnings("unchecked") + public Grid getGrid() { + return (Grid) cell.getGrid(); + } + + /** + * Get a reference to the cell that was active when this Event was fired. + * NOTE: do NOT rely on this information remaining accurate after + * leaving the event handler. + * + * @return a cell reference + */ + @SuppressWarnings("unchecked") + public CellReference getCell() { + return (CellReference) cell; + } + + /** + * Get a reference to the row that was active when this Event was fired. + * NOTE: do NOT rely on this information remaining accurate after + * leaving the event handler. + * + * @return a row data object + */ + @SuppressWarnings("unchecked") + public T getRow() { + return (T) cell.getRow(); + } + + /** + * Get the index of the row that was active when this Event was fired. NOTE: + * do NOT rely on this information remaining accurate after leaving + * the event handler. + * + * @return an integer value + */ + public int getRowIndex() { + return cell.getRowIndex(); + } + + /** + * Get a reference to the column that was active when this Event was fired. + * NOTE: do NOT rely on this information remaining accurate after + * leaving the event handler. + * + * @return a column object + */ + @SuppressWarnings("unchecked") + public Column getColumn() { + return (Column) cell.getColumn(); + } + + /** + * Get the index of the column that was active when this Event was fired. + * NOTE: do NOT rely on this information remaining accurate after + * leaving the event handler. + * + * @return an integer value + */ + public int getColumnIndex() { + return cell.getColumnIndex(); + } + +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java new file mode 100644 index 0000000000..4f9396a9f1 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Common handler interface for editor events + */ +public interface EditorEventHandler extends EventHandler { + + /** + * Action to perform when the editor has been opened + * + * @param e + * an editor open event object + */ + public void onEditorOpen(EditorOpenEvent e); + + /** + * Action to perform when the editor is re-opened on another row + * + * @param e + * an editor move event object + */ + public void onEditorMove(EditorMoveEvent e); + + /** + * Action to perform when the editor is closed + * + * @param e + * an editor close event object + */ + public void onEditorClose(EditorCloseEvent e); + +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java new file mode 100644 index 0000000000..0e5e2dcd7b --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.CellReference; + +/** + * Event that gets fired when an already open editor is closed and re-opened on + * another row + */ +public class EditorMoveEvent extends EditorEvent { + + public EditorMoveEvent(CellReference cell) { + super(cell); + } + + @Override + protected void dispatch(EditorEventHandler handler) { + handler.onEditorMove(this); + } +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java new file mode 100644 index 0000000000..df0171945f --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.CellReference; + +/** + * Event that gets fired when the editor is opened + */ +public class EditorOpenEvent extends EditorEvent { + + public EditorOpenEvent(CellReference cell) { + super(cell); + } + + @Override + protected void dispatch(EditorEventHandler handler) { + handler.onEditorOpen(this); + } + +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 9768a589f6..21cf84cc0d 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -120,6 +120,11 @@ import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; +import com.vaadin.client.widget.grid.events.EditorCloseEvent; +import com.vaadin.client.widget.grid.events.EditorEvent; +import com.vaadin.client.widget.grid.events.EditorEventHandler; +import com.vaadin.client.widget.grid.events.EditorMoveEvent; +import com.vaadin.client.widget.grid.events.EditorOpenEvent; import com.vaadin.client.widget.grid.events.FooterClickHandler; import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler; import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; @@ -1188,6 +1193,7 @@ public class Grid extends ResizeComposite implements + " remember to call success() or fail()?"); } }; + private final EditorRequestImpl.RequestCallback bindRequestCallback = new EditorRequestImpl.RequestCallback() { @Override public void onSuccess(EditorRequest request) { @@ -1313,6 +1319,7 @@ public class Grid extends ResizeComposite implements handler.cancel(request); state = State.INACTIVE; updateSelectionCheckboxesAsNeeded(true); + grid.fireEvent(new EditorCloseEvent(grid.eventCell)); } private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) { @@ -5706,6 +5713,19 @@ public class Grid extends ResizeComposite implements return editor; } + /** + * Add handler for editor open/move/close events + * + * @param handler + * editor handler object + * @return a {@link HandlerRegistration} object that can be used to remove + * the event handler + */ + public HandlerRegistration addEditorEventHandler(EditorEventHandler handler) { + return addHandler(handler, EditorEvent.TYPE); + + } + protected Escalator getEscalator() { return escalator; } @@ -6285,12 +6305,25 @@ public class Grid extends ResizeComposite implements } if (container == escalator.getBody() && editor.isEnabled()) { + + boolean wasOpen = editor.getState() != Editor.State.INACTIVE; + boolean opened = false; + if (event.getTypeInt() == Event.ONDBLCLICK) { editor.editRow(eventCell.getRowIndex()); - return true; + opened = true; } else if (event.getTypeInt() == Event.ONKEYDOWN && event.getKeyCode() == Editor.KEYCODE_SHOW) { editor.editRow(cellFocusHandler.rowWithFocus); + opened = true; + } + + if (opened) { + if (wasOpen) { + fireEvent(new EditorMoveEvent(eventCell)); + } else { + fireEvent(new EditorOpenEvent(eventCell)); + } return true; } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 534146e3d7..21bfd1b68f 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -57,6 +57,7 @@ import com.vaadin.data.RpcDataProviderExtension.DetailComponentManager; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory; import com.vaadin.data.fieldgroup.FieldGroup; +import com.vaadin.data.fieldgroup.FieldGroup.BindException; import com.vaadin.data.fieldgroup.FieldGroup.CommitException; import com.vaadin.data.fieldgroup.FieldGroupFieldFactory; import com.vaadin.data.sort.Sort; @@ -510,6 +511,100 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } + /** + * Interface for an editor event listener + */ + public interface EditorListener extends Serializable { + + public static final Method EDITOR_OPEN_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorOpened", + EditorOpenEvent.class); + public static final Method EDITOR_MOVE_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorMoved", + EditorMoveEvent.class); + public static final Method EDITOR_CLOSE_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorClosed", + EditorCloseEvent.class); + + /** + * Called when an editor is opened + * + * @param e + * an editor open event object + */ + public void editorOpened(EditorOpenEvent e); + + /** + * Called when an editor is reopened without closing it first + * + * @param e + * an editor move event object + */ + public void editorMoved(EditorMoveEvent e); + + /** + * Called when an editor is closed + * + * @param e + * an editor close event object + */ + public void editorClosed(EditorCloseEvent e); + + } + + /** + * Base class for editor related events + */ + public static abstract class EditorEvent extends Component.Event { + + private Object itemID; + + protected EditorEvent(Grid source, Object itemID) { + super(source); + this.itemID = itemID; + } + + /** + * Get the item (row) for which this editor was opened + */ + public Object getItem() { + return itemID; + } + + } + + /** + * This event gets fired when an editor is opened + */ + public static class EditorOpenEvent extends EditorEvent { + + public EditorOpenEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** + * This event gets fired when an editor is opened while another row is being + * edited (i.e. editor focus moves elsewhere) + */ + public static class EditorMoveEvent extends EditorEvent { + + public EditorMoveEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** + * This event gets fired when an editor is dismissed or closed by other + * means. + */ + public static class EditorCloseEvent extends EditorEvent { + + public EditorCloseEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + /** * Default error handler for the editor * @@ -3808,6 +3903,24 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, detailComponentManager.getAndResetConnectorChanges(), fetchId); } + + @Override + public void editorOpen(String rowKey) { + fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper() + .getItemId(rowKey))); + } + + @Override + public void editorMove(String rowKey) { + fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper() + .getItemId(rowKey))); + } + + @Override + public void editorClose(String rowKey) { + fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper() + .getItemId(rowKey))); + } }); registerRpc(new EditorServerRpc() { @@ -5875,6 +5988,37 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, return getState(false).editorCancelCaption; } + /** + * Add an editor event listener + * + * @param listener + * the event listener object to add + */ + public void addEditorListener(EditorListener listener) { + addListener(GridConstants.EDITOR_OPEN_EVENT_ID, EditorOpenEvent.class, + listener, EditorListener.EDITOR_OPEN_METHOD); + addListener(GridConstants.EDITOR_MOVE_EVENT_ID, EditorMoveEvent.class, + listener, EditorListener.EDITOR_MOVE_METHOD); + addListener(GridConstants.EDITOR_CLOSE_EVENT_ID, + EditorCloseEvent.class, listener, + EditorListener.EDITOR_CLOSE_METHOD); + } + + /** + * Remove an editor event listener + * + * @param listener + * the event listener object to remove + */ + public void removeEditorListener(EditorListener listener) { + removeListener(GridConstants.EDITOR_OPEN_EVENT_ID, + EditorOpenEvent.class, listener); + removeListener(GridConstants.EDITOR_MOVE_EVENT_ID, + EditorMoveEvent.class, listener); + removeListener(GridConstants.EDITOR_CLOSE_EVENT_ID, + EditorCloseEvent.class, listener); + } + @Override public void addItemClickListener(ItemClickListener listener) { addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, @@ -6169,6 +6313,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, result.add("footer-visible"); result.add("editor-error-handler"); result.add("height-mode"); + return result; } } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridConstants.java b/shared/src/com/vaadin/shared/ui/grid/GridConstants.java index 0606e4b1cc..5b2ac96975 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridConstants.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridConstants.java @@ -74,4 +74,19 @@ public final class GridConstants implements Serializable { /** The default cancel button caption in the editor */ public static final String DEFAULT_CANCEL_CAPTION = "Cancel"; + + /** + * Event ID constant for editor open event + */ + public static final String EDITOR_OPEN_EVENT_ID = "editorOpen"; + + /** + * Event ID constant for editor move event + */ + public static final String EDITOR_MOVE_EVENT_ID = "editorMove"; + + /** + * Event ID constant for editor close event + */ + public static final String EDITOR_CLOSE_EVENT_ID = "editorClose"; } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index dca55c11c4..99b339765a 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -36,6 +36,31 @@ public interface GridServerRpc extends ServerRpc { void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated); + /** + * Informs the server that the editor was opened (fresh) on a certain row + * + * @param rowKey + * a key identifying item the editor was opened on + */ + void editorOpen(String rowKey); + + /** + * Informs the server that the editor was reopened (without closing it in + * between) on another row + * + * @param rowKey + * a key identifying item the editor was opened on + */ + void editorMove(String rowKey); + + /** + * Informs the server that the editor was closed + * + * @param rowKey + * a key identifying item the editor was opened on + */ + void editorClose(String rowKey); + /** * Informs the server that an item has been clicked in Grid. * diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 8d8be84169..181f99a2c7 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -57,6 +57,10 @@ import com.vaadin.ui.Grid.ColumnReorderListener; import com.vaadin.ui.Grid.ColumnVisibilityChangeEvent; import com.vaadin.ui.Grid.ColumnVisibilityChangeListener; import com.vaadin.ui.Grid.DetailsGenerator; +import com.vaadin.ui.Grid.EditorCloseEvent; +import com.vaadin.ui.Grid.EditorListener; +import com.vaadin.ui.Grid.EditorMoveEvent; +import com.vaadin.ui.Grid.EditorOpenEvent; import com.vaadin.ui.Grid.FooterCell; import com.vaadin.ui.Grid.HeaderCell; import com.vaadin.ui.Grid.HeaderRow; @@ -401,6 +405,7 @@ public class GridBasicFeatures extends AbstractComponentTest { } protected void createGridActions() { + LinkedHashMap primaryStyleNames = new LinkedHashMap(); primaryStyleNames.put("v-grid", "v-grid"); primaryStyleNames.put("v-escalator", "v-escalator"); @@ -1218,6 +1223,30 @@ public class GridBasicFeatures extends AbstractComponentTest { c.setEditorCancelCaption("ʃǝɔuɐↃ"); } }, null); + + createClickAction("Add editor state listener", "Editor", + new Command() { + @Override + public void execute(Grid grid, String value, Object data) { + grid.addEditorListener(new EditorListener() { + @Override + public void editorOpened(EditorOpenEvent e) { + log("Editor opened"); + } + + @Override + public void editorMoved(EditorMoveEvent e) { + log("Editor moved"); + } + + @Override + public void editorClosed(EditorCloseEvent e) { + log("Editor closed"); + } + }); + } + }, null); + } @SuppressWarnings("boxing") -- cgit v1.2.3 From 469a53e1256f143f506f8a3b59ef7fc505353495 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 25 May 2015 12:09:23 +0300 Subject: Add unbuffered editing mode to Grid Save and cancel buttons are hidden and the backing field group is set to unbuffered mode. Change-Id: I7da46ae3f1b84cc5ac8c918be38919962aff88ed --- client/src/com/vaadin/client/widgets/Grid.java | 38 ++++++++++++++++++++-- server/src/com/vaadin/ui/Grid.java | 33 +++++++++++++++++++ .../src/com/vaadin/shared/ui/grid/GridState.java | 4 +++ .../grid/basicfeatures/GridBasicFeatures.java | 8 +++++ .../grid/basicfeatures/server/GridEditorTest.java | 37 +++++++++++++++++++++ 5 files changed, 118 insertions(+), 2 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 21cf84cc0d..bf5494291f 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -1225,6 +1225,7 @@ public class Grid extends ResizeComposite implements /** A set of all the columns that display an error flag. */ private final Set> columnErrors = new HashSet>(); + private boolean buffered = true; public Editor() { saveButton = new Button(); @@ -1504,8 +1505,10 @@ public class Grid extends ResizeComposite implements messageAndButtonsWrapper.appendChild(buttonsWrapper); } - attachWidget(saveButton, buttonsWrapper); - attachWidget(cancelButton, buttonsWrapper); + if (isBuffered()) { + attachWidget(saveButton, buttonsWrapper); + attachWidget(cancelButton, buttonsWrapper); + } updateHorizontalScrollPosition(); @@ -1715,6 +1718,14 @@ public class Grid extends ResizeComposite implements public boolean isEditorColumnError(Column column) { return columnErrors.contains(column); } + + public void setBuffered(boolean buffered) { + this.buffered = buffered; + } + + public boolean isBuffered() { + return buffered; + } } public static abstract class AbstractGridKeyEvent @@ -7840,4 +7851,27 @@ public class Grid extends ResizeComposite implements public void focus() { setFocus(true); } + + /** + * Sets the buffered editor mode. + * + * @since + * @param editorUnbuffered + * true to enable buffered editor, + * false to disable it + */ + public void setEditorBuffered(boolean editorBuffered) { + editor.setBuffered(editorBuffered); + } + + /** + * Gets the buffered editor mode. + * + * @since + * @return true if buffered editor is enabled, + * false otherwise + */ + public boolean isEditorBuffered() { + return editor.isBuffered(); + } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 21bfd1b68f..fc5adbbff0 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -6019,6 +6019,39 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, EditorCloseEvent.class, listener); } + /** + * Sets the buffered editor mode. The default mode is buffered ( + * true). + * + * @since + * @param editorBuffered + * true to enable buffered editor, + * false to disable it + * @throws IllegalStateException + * If editor is active while attempting to change the buffered + * mode. + */ + public void setEditorBuffered(boolean editorBuffered) + throws IllegalStateException { + if (isEditorActive()) { + throw new IllegalStateException( + "Can't change editor unbuffered mode while editor is active."); + } + getState().editorBuffered = editorBuffered; + editorFieldGroup.setBuffered(editorBuffered); + } + + /** + * Gets the buffered editor mode. + * + * @since + * @return true if buffered editor is enabled, + * false otherwise + */ + public boolean isEditorBuffered() { + return getState().editorBuffered; + } + @Override public void addItemClickListener(ItemClickListener listener) { addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index bb35659591..3faa4b441c 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -154,6 +154,10 @@ public class GridState extends TabIndexState { /** The enabled state of the editor interface */ public boolean editorEnabled = false; + /** Buffered editor mode */ + @DelegateToWidget + public boolean editorBuffered = true; + /** Whether row data might contain generated row styles */ public boolean hasRowStyleGenerator; /** Whether row data might contain generated cell styles */ diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 181f99a2c7..ba19d7ee72 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -1176,6 +1176,14 @@ public class GridBasicFeatures extends AbstractComponentTest { } }); + createBooleanAction("Buffered mode", "Editor", true, + new Command() { + @Override + public void execute(Grid c, Boolean value, Object data) { + c.setEditorBuffered(value); + } + }); + createClickAction("Edit item 5", "Editor", new Command() { @Override public void execute(Grid c, String value, Object data) { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java index f37a94358a..8a8792c351 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java @@ -47,6 +47,8 @@ public class GridEditorTest extends GridBasicFeaturesTest { "Editor", "Edit item 100" }; private static final String[] TOGGLE_EDIT_ENABLED = new String[] { "Component", "Editor", "Enabled" }; + private static final String[] TOGGLE_EDITOR_BUFFERED_ENABLED = new String[] { + "Component", "Editor", "Buffered mode" }; @Before public void setUp() { @@ -302,6 +304,41 @@ public class GridEditorTest extends GridBasicFeaturesTest { getGridElement().getEditor().isEditable(3)); } + @Test + public void testEditorUnbufferedShowsNoButtons() { + selectMenuPath(TOGGLE_EDITOR_BUFFERED_ENABLED); + selectMenuPath(EDIT_ITEM_5); + + assertEditorOpen(); + + boolean saveButtonFound = true; + try { + getSaveButton(); + } catch (NoSuchElementException e) { + saveButtonFound = false; + } + assertFalse("Save button should not be visible in unbuffered mode.", + saveButtonFound); + + boolean cancelButtonFound = true; + try { + getCancelButton(); + } catch (NoSuchElementException e) { + cancelButtonFound = false; + } + assertFalse("Cancel button should not be visible in unbuffered mode.", + cancelButtonFound); + } + + @Test + public void testEditorUnbufferedWhileOpen() { + selectMenuPath(EDIT_ITEM_5); + selectMenuPath(TOGGLE_EDITOR_BUFFERED_ENABLED); + assertEditorOpen(); + boolean thrown = logContainsText("Exception occured, java.lang.IllegalStateException"); + assertTrue("IllegalStateException thrown", thrown); + } + private WebElement getSaveButton() { return getDriver().findElement(By.className("v-grid-editor-save")); } -- cgit v1.2.3 From 40dcbc3cfaa438c9b879720c9012331dd85ca694 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Thu, 2 Jul 2015 17:34:27 +0300 Subject: Refactor RpcDataProviderExtension to use DataGenerators Change-Id: I8c809b6fac827df730c6622fb6790410c6c5bd81 --- .../vaadin/client/connectors/GridConnector.java | 33 +--- server/src/com/vaadin/data/DataGenerator.java | 49 +++++ .../com/vaadin/data/RpcDataProviderExtension.java | 220 +++++++-------------- server/src/com/vaadin/ui/Grid.java | 147 +++++++++++++- .../vaadin/tests/server/renderer/RendererTest.java | 26 +-- .../src/com/vaadin/shared/ui/grid/GridState.java | 5 - 6 files changed, 281 insertions(+), 199 deletions(-) create mode 100644 server/src/com/vaadin/data/DataGenerator.java (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 052d8ee368..bee9cedc43 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -38,7 +38,6 @@ import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.DeferredWorker; import com.vaadin.client.MouseEventDetailsBuilder; -import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; @@ -119,8 +118,8 @@ import elemental.json.JsonValue; public class GridConnector extends AbstractHasComponentsConnector implements SimpleManagedLayout, DeferredWorker { - private static final class CustomCellStyleGenerator implements - CellStyleGenerator { + private static final class CustomStyleGenerator implements + CellStyleGenerator, RowStyleGenerator { @Override public String getStyle(CellReference cellReference) { JsonObject row = cellReference.getRow(); @@ -146,10 +145,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - } - - private static final class CustomRowStyleGenerator implements - RowStyleGenerator { @Override public String getStyle(RowReference rowReference) { JsonObject row = rowReference.getRow(); @@ -159,7 +154,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements return null; } } - } /** @@ -734,6 +728,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements private String lastKnownTheme = null; private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator(); + private final CustomStyleGenerator styleGenerator = new CustomStyleGenerator(); private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher( getRpcProxy(GridServerRpc.class)); @@ -873,6 +868,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().addBodyClickHandler(itemClickHandler); getWidget().addBodyDoubleClickHandler(itemClickHandler); + /* Style Generators */ + getWidget().setCellStyleGenerator(styleGenerator); + getWidget().setRowStyleGenerator(styleGenerator); + getWidget().addSortHandler(new SortHandler() { @Override public void sort(SortEvent event) { @@ -1289,24 +1288,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - @OnStateChange("hasCellStyleGenerator") - private void onCellStyleGeneratorChange() { - if (getState().hasCellStyleGenerator) { - getWidget().setCellStyleGenerator(new CustomCellStyleGenerator()); - } else { - getWidget().setCellStyleGenerator(null); - } - } - - @OnStateChange("hasRowStyleGenerator") - private void onRowStyleGeneratorChange() { - if (getState().hasRowStyleGenerator) { - getWidget().setRowStyleGenerator(new CustomRowStyleGenerator()); - } else { - getWidget().setRowStyleGenerator(null); - } - } - private void updateSelectionFromState() { boolean changed = false; diff --git a/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java new file mode 100644 index 0000000000..f025623a3e --- /dev/null +++ b/server/src/com/vaadin/data/DataGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.data; + +import java.io.Serializable; + +import com.vaadin.ui.Grid.AbstractGridExtension; +import com.vaadin.ui.Grid.AbstractRenderer; + +import elemental.json.JsonObject; + +/** + * Interface for {@link AbstractGridExtension}s that allows adding data to row + * objects being sent to client by the {@link RpcDataProviderExtension}. + *

+ * {@link AbstractRenderer} implements this interface to provide encoded data to + * client for {@link Renderer}s automatically. + * + * @since + * @author Vaadin Ltd + */ +public interface DataGenerator extends Serializable { + + /** + * Adds data to row object for given item and item id being sent to client. + * + * @param itemId + * item id of item + * @param item + * item being sent to client + * @param rowData + * row object being sent to client + */ + public void generateData(Object itemId, Item item, JsonObject rowData); + +} diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 9d18736ba8..71b597ff1d 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -23,11 +23,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.logging.Logger; import com.google.gwt.thirdparty.guava.common.collect.BiMap; import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; @@ -43,10 +41,8 @@ import com.vaadin.data.Container.ItemSetChangeNotifier; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Property.ValueChangeNotifier; -import com.vaadin.data.util.converter.Converter; import com.vaadin.server.AbstractExtension; import com.vaadin.server.ClientConnector; -import com.vaadin.server.KeyMapper; import com.vaadin.shared.data.DataProviderRpc; import com.vaadin.shared.data.DataRequestRpc; import com.vaadin.shared.ui.grid.DetailsConnectorChange; @@ -56,13 +52,9 @@ import com.vaadin.shared.ui.grid.Range; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; import com.vaadin.ui.Grid; -import com.vaadin.ui.Grid.CellReference; -import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.Grid.DetailsGenerator; import com.vaadin.ui.Grid.RowReference; -import com.vaadin.ui.Grid.RowStyleGenerator; -import com.vaadin.ui.renderers.Renderer; import elemental.json.Json; import elemental.json.JsonArray; @@ -93,7 +85,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * itemId ⇆ key mapping is not needed anymore. In other words, this * doesn't leak memory. */ - public class DataProviderKeyMapper implements Serializable { + public class DataProviderKeyMapper implements Serializable, DataGenerator { private final BiMap itemIdToKey = HashBiMap.create(); private Set pinnedItemIds = new HashSet(); private long rollingIndex = 0; @@ -125,7 +117,7 @@ public class RpcDataProviderExtension extends AbstractExtension { for (Object itemId : itemSet) { itemIdToKey.put(itemId, getKey(itemId)); - if (visibleDetails.contains(itemId)) { + if (detailComponentManager.visibleDetails.contains(itemId)) { detailComponentManager.createDetails(itemId, indexOf(itemId)); } @@ -280,6 +272,16 @@ public class RpcDataProviderExtension extends AbstractExtension { public boolean isPinned(Object itemId) { return pinnedItemIds.contains(itemId); } + + /** + * {@inheritDoc} + * + * @since + */ + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + rowData.put(GridState.JSONKEY_ROWKEY, getKey(itemId)); + } } /** @@ -601,7 +603,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * @since 7.5.0 * @author Vaadin Ltd */ - public static final class DetailComponentManager implements Serializable { + public static final class DetailComponentManager implements DataGenerator { /** * This map represents all the components that have been requested for * each item id. @@ -649,6 +651,12 @@ public class RpcDataProviderExtension extends AbstractExtension { */ private final Map emptyDetails = Maps.newHashMap(); + /** + * This map represents all the details that are user-defined as visible. + * This does not reflect the status in the DOM. + */ + private Set visibleDetails = new HashSet(); + private Grid grid; /** @@ -856,6 +864,18 @@ public class RpcDataProviderExtension extends AbstractExtension { } this.grid = grid; } + + /** + * {@inheritDoc} + * + * @since + */ + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + if (visibleDetails.contains(itemId)) { + rowData.put(GridState.JSONKEY_DETAILS_VISIBLE, true); + } + } } private final Indexed container; @@ -939,14 +959,9 @@ public class RpcDataProviderExtension extends AbstractExtension { private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper(); - private KeyMapper columnKeys; - /** RpcDataProvider should send the current cache again. */ private boolean refreshCache = false; - private RowReference rowReference; - private CellReference cellReference; - /** Set of updated item ids */ private Set updatedItemIds = new LinkedHashSet(); @@ -959,14 +974,10 @@ public class RpcDataProviderExtension extends AbstractExtension { /** Size possibly changed with a bare ItemSetChangeEvent */ private boolean bareItemSetTriggeredSizeChange = false; - /** - * This map represents all the details that are user-defined as visible. - * This does not reflect the status in the DOM. - */ - private Set visibleDetails = new HashSet(); - private final DetailComponentManager detailComponentManager = new DetailComponentManager(); + private Set dataGenerators = new LinkedHashSet(); + /** * Creates a new data provider using the given container. * @@ -1006,6 +1017,8 @@ public class RpcDataProviderExtension extends AbstractExtension { .addItemSetChangeListener(itemListener); } + addDataGenerator(keyMapper); + addDataGenerator(detailComponentManager); } /** @@ -1090,73 +1103,14 @@ public class RpcDataProviderExtension extends AbstractExtension { private JsonValue getRowData(Collection columns, Object itemId) { Item item = container.getItem(itemId); - JsonObject rowData = Json.createObject(); - - Grid grid = getGrid(); - - for (Column column : columns) { - Object propertyId = column.getPropertyId(); - - Object propertyValue = item.getItemProperty(propertyId).getValue(); - JsonValue encodedValue = encodeValue(propertyValue, - column.getRenderer(), column.getConverter(), - grid.getLocale()); - - rowData.put(columnKeys.key(propertyId), encodedValue); - } - final JsonObject rowObject = Json.createObject(); - rowObject.put(GridState.JSONKEY_DATA, rowData); - rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId)); - - if (visibleDetails.contains(itemId)) { - rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true); - } - - rowReference.set(itemId); - - CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator(); - if (cellStyleGenerator != null) { - setGeneratedCellStyles(cellStyleGenerator, rowObject, columns); - } - RowStyleGenerator rowStyleGenerator = grid.getRowStyleGenerator(); - if (rowStyleGenerator != null) { - setGeneratedRowStyles(rowStyleGenerator, rowObject); + for (DataGenerator dg : dataGenerators) { + dg.generateData(itemId, item, rowObject); } return rowObject; } - private void setGeneratedCellStyles(CellStyleGenerator generator, - JsonObject rowObject, Collection columns) { - JsonObject cellStyles = null; - for (Column column : columns) { - Object propertyId = column.getPropertyId(); - cellReference.set(propertyId); - String style = generator.getStyle(cellReference); - if (style != null && !style.isEmpty()) { - if (cellStyles == null) { - cellStyles = Json.createObject(); - } - - String columnKey = columnKeys.key(propertyId); - cellStyles.put(columnKey, style); - } - } - if (cellStyles != null) { - rowObject.put(GridState.JSONKEY_CELLSTYLES, cellStyles); - } - - } - - private void setGeneratedRowStyles(RowStyleGenerator generator, - JsonObject rowObject) { - String rowStyle = generator.getStyle(rowReference); - if (rowStyle != null && !rowStyle.isEmpty()) { - rowObject.put(GridState.JSONKEY_ROWSTYLE, rowStyle); - } - } - /** * Makes the data source available to the given {@link Grid} component. * @@ -1165,12 +1119,37 @@ public class RpcDataProviderExtension extends AbstractExtension { * @param columnKeys * the key mapper for columns */ - public void extend(Grid component, KeyMapper columnKeys) { - this.columnKeys = columnKeys; + public void extend(Grid component) { detailComponentManager.setGrid(component); super.extend(component); } + /** + * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}. + * DataGenerators are called when sending row data to client. If given + * DataGenerator is already added, this method does nothing. + * + * @since + * @param generator + * generator to add + */ + public void addDataGenerator(DataGenerator generator) { + dataGenerators.add(generator); + } + + /** + * Removes a {@link DataGenerator} from this + * {@code RpcDataProviderExtension}. If given DataGenerator is not added to + * this data provider, this method does nothing. + * + * @since + * @param generator + * generator to remove + */ + public void removeDataGenerator(DataGenerator generator) { + dataGenerators.remove(generator); + } + /** * Informs the client side that new rows have been inserted into the data * source. @@ -1281,11 +1260,7 @@ public class RpcDataProviderExtension extends AbstractExtension { .removeItemSetChangeListener(itemListener); } - } else if (parent instanceof Grid) { - Grid grid = (Grid) parent; - rowReference = new RowReference(grid); - cellReference = new CellReference(rowReference); - } else { + } else if (!(parent instanceof Grid)) { throw new IllegalStateException( "Grid is the only accepted parent type"); } @@ -1321,62 +1296,6 @@ public class RpcDataProviderExtension extends AbstractExtension { return (Grid) getParent(); } - /** - * Converts and encodes the given data model property value using the given - * converter and renderer. This method is public only for testing purposes. - * - * @param renderer - * the renderer to use - * @param converter - * the converter to use - * @param modelValue - * the value to convert and encode - * @param locale - * the locale to use in conversion - * @return an encoded value ready to be sent to the client - */ - public static JsonValue encodeValue(Object modelValue, - Renderer renderer, Converter converter, Locale locale) { - Class presentationType = renderer.getPresentationType(); - T presentationValue; - - if (converter == null) { - try { - presentationValue = presentationType.cast(modelValue); - } catch (ClassCastException e) { - if (presentationType == String.class) { - // If there is no converter, just fallback to using - // toString(). - // modelValue can't be null as Class.cast(null) will always - // succeed - presentationValue = (T) modelValue.toString(); - } else { - throw new Converter.ConversionException( - "Unable to convert value of type " - + modelValue.getClass().getName() - + " to presentation type " - + presentationType.getName() - + ". No converter is set and the types are not compatible."); - } - } - } else { - assert presentationType.isAssignableFrom(converter - .getPresentationType()); - @SuppressWarnings("unchecked") - Converter safeConverter = (Converter) converter; - presentationValue = safeConverter.convertToPresentation(modelValue, - safeConverter.getPresentationType(), locale); - } - - JsonValue encodedValue = renderer.encode(presentationValue); - - return encodedValue; - } - - private static Logger getLogger() { - return Logger.getLogger(RpcDataProviderExtension.class.getName()); - } - /** * Marks a row's details to be visible or hidden. *

@@ -1394,7 +1313,7 @@ public class RpcDataProviderExtension extends AbstractExtension { final boolean modified; if (visible) { - modified = visibleDetails.add(itemId); + modified = detailComponentManager.visibleDetails.add(itemId); /* * We don't want to create the component here, since the component @@ -1405,7 +1324,7 @@ public class RpcDataProviderExtension extends AbstractExtension { */ } else { - modified = visibleDetails.remove(itemId); + modified = detailComponentManager.visibleDetails.remove(itemId); /* * Here we can try to destroy the component no matter what. The @@ -1435,7 +1354,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * visible in the DOM */ public boolean isDetailsVisible(Object itemId) { - return visibleDetails.contains(itemId); + return detailComponentManager.visibleDetails.contains(itemId); } /** @@ -1444,7 +1363,8 @@ public class RpcDataProviderExtension extends AbstractExtension { * @since 7.5.0 */ public void refreshDetails() { - for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { + for (Object itemId : ImmutableSet + .copyOf(detailComponentManager.visibleDetails)) { detailComponentManager.refresh(itemId); } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index f7d49e6419..af89064411 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -52,6 +53,7 @@ import com.vaadin.data.Container.PropertySetChangeEvent; import com.vaadin.data.Container.PropertySetChangeListener; import com.vaadin.data.Container.PropertySetChangeNotifier; import com.vaadin.data.Container.Sortable; +import com.vaadin.data.DataGenerator; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.RpcDataProviderExtension; @@ -1547,6 +1549,60 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, public String getStyle(CellReference cellReference); } + /** + * Class for generating all row and cell related data for the essential + * parts of Grid. + */ + private class RowDataGenerator implements DataGenerator { + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + RowReference r = new RowReference(Grid.this); + r.set(itemId); + + if (rowStyleGenerator != null) { + String style = rowStyleGenerator.getStyle(r); + if (style != null && !style.isEmpty()) { + rowData.put(GridState.JSONKEY_ROWSTYLE, style); + } + } + + JsonObject cellStyles = Json.createObject(); + JsonObject cellData = Json.createObject(); + for (Column column : getColumns()) { + Object propertyId = column.getPropertyId(); + String columnId = columnKeys.key(propertyId); + + cellData.put(columnId, getRendererData(column, item)); + + if (cellStyleGenerator != null) { + CellReference c = new CellReference(r); + c.set(propertyId); + + String style = cellStyleGenerator.getStyle(c); + if (style != null && !style.isEmpty()) { + cellStyles.put(columnId, style); + } + } + } + + if (cellStyleGenerator != null && cellStyles.keys().length > 0) { + rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles); + } + rowData.put(GridState.JSONKEY_DATA, cellData); + } + + private JsonValue getRendererData(Column column, Item item) { + Converter converter = column.getConverter(); + Object propertyId = column.getPropertyId(); + Object modelValue = item.getItemProperty(propertyId).getValue(); + Renderer renderer = column.getRenderer(); + + return AbstractRenderer.encodeValue(modelValue, renderer, + converter, getLocale()); + } + } + /** * Abstract base class for Grid header and footer sections. * @@ -3462,10 +3518,67 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, return JsonCodec.encode(value, null, type, getUI().getConnectorTracker()).getEncodedValue(); } + + /** + * Converts and encodes the given data model property value using the + * given converter and renderer. This method is public only for testing + * purposes. + * + * @param renderer + * the renderer to use + * @param converter + * the converter to use + * @param modelValue + * the value to convert and encode + * @param locale + * the locale to use in conversion + * @return an encoded value ready to be sent to the client + */ + public static JsonValue encodeValue(Object modelValue, + Renderer renderer, Converter converter, Locale locale) { + Class presentationType = renderer.getPresentationType(); + T presentationValue; + + if (converter == null) { + try { + presentationValue = presentationType.cast(modelValue); + } catch (ClassCastException e) { + if (presentationType == String.class) { + // If there is no converter, just fallback to using + // toString(). modelValue can't be null as + // Class.cast(null) will always succeed + presentationValue = (T) modelValue.toString(); + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + modelValue.getClass().getName() + + " to presentation type " + + presentationType.getName() + + ". No converter is set and the types are not compatible."); + } + } + } else { + assert presentationType.isAssignableFrom(converter + .getPresentationType()); + @SuppressWarnings("unchecked") + Converter safeConverter = (Converter) converter; + presentationValue = safeConverter + .convertToPresentation(modelValue, + safeConverter.getPresentationType(), locale); + } + + JsonValue encodedValue = renderer.encode(presentationValue); + + return encodedValue; + } } /** * An abstract base class for server-side Grid extensions. + *

+ * Note: If the extension is an instance of {@link DataGenerator} it will + * automatically register itself to {@link RpcDataProviderExtension} of + * extended Grid. On remove this registration is automatically removed. * * @since 7.5 */ @@ -3490,6 +3603,26 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, extend(grid); } + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + + if (this instanceof DataGenerator) { + getParentGrid().datasourceExtension + .addDataGenerator((DataGenerator) this); + } + } + + @Override + public void remove() { + if (this instanceof DataGenerator) { + getParentGrid().datasourceExtension + .removeDataGenerator((DataGenerator) this); + } + + super.remove(); + } + /** * Gets the item id for a row key. *

@@ -3531,9 +3664,14 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, if (getParent() instanceof Grid) { Grid grid = (Grid) getParent(); return grid; + } else if (getParent() == null) { + throw new IllegalStateException( + "Renderer is not attached to any parent"); } else { throw new IllegalStateException( - "Renderers can be used only with Grid"); + "Renderers can be used only with Grid. Extended " + + getParent().getClass().getSimpleName() + + " instead"); } } } @@ -4153,7 +4291,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } datasourceExtension = new RpcDataProviderExtension(container); - datasourceExtension.extend(this, columnKeys); + datasourceExtension.extend(this); + datasourceExtension.addDataGenerator(new RowDataGenerator()); detailComponentManager = datasourceExtension .getDetailComponentManager(); @@ -5620,8 +5759,6 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) { this.cellStyleGenerator = cellStyleGenerator; - getState().hasCellStyleGenerator = (cellStyleGenerator != null); - datasourceExtension.refreshCache(); } @@ -5644,8 +5781,6 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { this.rowStyleGenerator = rowStyleGenerator; - getState().hasRowStyleGenerator = (rowStyleGenerator != null); - datasourceExtension.refreshCache(); } diff --git a/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java b/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java index eb07fae07f..cea8df0ba6 100644 --- a/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java +++ b/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java @@ -15,8 +15,17 @@ */ package com.vaadin.tests.server.renderer; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import java.util.Date; +import java.util.Locale; + +import org.junit.Before; +import org.junit.Test; + import com.vaadin.data.Item; -import com.vaadin.data.RpcDataProviderExtension; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.StringToIntegerConverter; @@ -24,22 +33,15 @@ import com.vaadin.server.VaadinSession; import com.vaadin.tests.server.component.grid.TestGrid; import com.vaadin.tests.util.AlwaysLockedVaadinSession; import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.AbstractRenderer; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.renderers.ButtonRenderer; import com.vaadin.ui.renderers.DateRenderer; import com.vaadin.ui.renderers.HtmlRenderer; import com.vaadin.ui.renderers.NumberRenderer; import com.vaadin.ui.renderers.TextRenderer; -import elemental.json.JsonValue; -import org.junit.Before; -import org.junit.Test; - -import java.util.Date; -import java.util.Locale; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; +import elemental.json.JsonValue; public class RendererTest { @@ -259,7 +261,7 @@ public class RendererTest { } private JsonValue render(Column column, Object value) { - return RpcDataProviderExtension.encodeValue(value, - column.getRenderer(), column.getConverter(), grid.getLocale()); + return AbstractRenderer.encodeValue(value, column.getRenderer(), + column.getConverter(), grid.getLocale()); } } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index c4121cbf45..c455ffe23b 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -160,11 +160,6 @@ public class GridState extends TabIndexState { @DelegateToWidget public boolean editorBuffered = true; - /** Whether row data might contain generated row styles */ - public boolean hasRowStyleGenerator; - /** Whether row data might contain generated cell styles */ - public boolean hasCellStyleGenerator; - /** The caption for the save button in the editor */ @DelegateToWidget public String editorSaveCaption = GridConstants.DEFAULT_SAVE_CAPTION; -- cgit v1.2.3 From 80058d9429940c376c63c086b1cf79848fe1a699 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Thu, 2 Jul 2015 16:37:01 +0300 Subject: Add row and cell description generators to Grid (#18481) Change-Id: I940399d986eb6970df687880645fafc157dab432 --- .../vaadin/client/connectors/GridConnector.java | 40 ++++ client/src/com/vaadin/client/widgets/Grid.java | 22 +++ server/src/com/vaadin/ui/Grid.java | 217 +++++++++++++++++---- .../src/com/vaadin/shared/ui/grid/GridState.java | 17 ++ .../grid/basicfeatures/GridBasicFeatures.java | 43 ++++ .../GridDescriptionGeneratorTest.java | 74 +++++++ 6 files changed, 380 insertions(+), 33 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index d42041670e..3c83fb2c24 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -31,6 +31,7 @@ import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; @@ -38,6 +39,7 @@ import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.DeferredWorker; import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.TooltipInfo; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; @@ -1456,4 +1458,42 @@ public class GridConnector extends AbstractHasComponentsConnector implements public DetailsListener getDetailsListener() { return detailsListener; } + + @Override + public boolean hasTooltip() { + return getState().hasDescriptions || super.hasTooltip(); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + CellReference cell = getWidget().getCellReference(element); + + if (cell != null) { + JsonObject row = cell.getRow(); + if (row == null) { + return null; + } + + Column column = cell.getColumn(); + if (!(column instanceof CustomGridColumn)) { + // Selection checkbox column + return null; + } + CustomGridColumn c = (CustomGridColumn) column; + + JsonObject cellDescriptions = row + .getObject(GridState.JSONKEY_CELLDESCRIPTION); + + if (cellDescriptions != null && cellDescriptions.hasKey(c.id)) { + return new TooltipInfo(cellDescriptions.getString(c.id)); + } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) { + return new TooltipInfo( + row.getString(GridState.JSONKEY_ROWDESCRIPTION)); + } else { + return null; + } + } + + return super.getTooltipInfo(element); + } } diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 0555df3c1f..cf05e7e53a 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -8173,4 +8173,26 @@ public class Grid extends ResizeComposite implements public EventCellReference getEventCell() { return eventCell; } + + /** + * Returns a CellReference for the cell to which the given element belongs + * to. + * + * @since + * @param element + * Element to find from the cell's content. + * @return CellReference or null if cell was not found. + */ + public CellReference getCellReference(Element element) { + RowContainer container = getEscalator().findRowContainer(element); + if (container != null) { + Cell cell = container.getCell(element); + if (cell != null) { + EventCellReference cellRef = new EventCellReference(this); + cellRef.set(cell, getSectionFromContainer(container)); + return cellRef; + } + } + return null; + } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index f025b6e1c0..ab4236fdf0 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -1514,39 +1514,88 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } /** - * Callback interface for generating custom style names for data rows + * A callback interface for generating custom style names for Grid rows. * * @see Grid#setRowStyleGenerator(RowStyleGenerator) */ public interface RowStyleGenerator extends Serializable { /** - * Called by Grid to generate a style name for a row + * Called by Grid to generate a style name for a row. * - * @param rowReference - * The row to generate a style for + * @param row + * the row to generate a style for * @return the style name to add to this row, or {@code null} to not set * any style */ - public String getStyle(RowReference rowReference); + public String getStyle(RowReference row); } /** - * Callback interface for generating custom style names for cells + * A callback interface for generating custom style names for Grid cells. * * @see Grid#setCellStyleGenerator(CellStyleGenerator) */ public interface CellStyleGenerator extends Serializable { /** - * Called by Grid to generate a style name for a column + * Called by Grid to generate a style name for a column. * - * @param cellReference - * The cell to generate a style for + * @param cell + * the cell to generate a style for * @return the style name to add to this cell, or {@code null} to not * set any style */ - public String getStyle(CellReference cellReference); + public String getStyle(CellReference cell); + } + + /** + * A callback interface for generating optional descriptions (tooltips) for + * Grid rows. If a description is generated for a row, it is used for all + * the cells in the row for which a {@link CellDescriptionGenerator cell + * description} is not generated. + * + * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator) + * + * @since + */ + public interface RowDescriptionGenerator extends Serializable { + + /** + * Called by Grid to generate a description (tooltip) for a row. The + * description may contain HTML which is rendered directly; if this is + * not desired the returned string must be escaped by the implementing + * method. + * + * @param row + * the row to generate a description for + * @return the row description or {@code null} for no description + */ + public String getDescription(RowReference row); + } + + /** + * A callback interface for generating optional descriptions (tooltips) for + * Grid cells. If a cell has both a {@link RowDescriptionGenerator row + * description} and a cell description, the latter has precedence. + * + * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator) + * + * @since + */ + public interface CellDescriptionGenerator extends Serializable { + + /** + * Called by Grid to generate a description (tooltip) for a cell. The + * description may contain HTML which is rendered directly; if this is + * not desired the returned string must be escaped by the implementing + * method. + * + * @param cell + * the cell to generate a description for + * @return the cell description or {@code null} for no description + */ + public String getDescription(CellReference cell); } /** @@ -1555,51 +1604,83 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ private class RowDataGenerator implements DataGenerator { + private void put(String key, String value, JsonObject object) { + if (value != null && !value.isEmpty()) { + object.put(key, value); + } + } + @Override public void generateData(Object itemId, Item item, JsonObject rowData) { - RowReference r = new RowReference(Grid.this); - r.set(itemId); + RowReference row = new RowReference(Grid.this); + row.set(itemId); if (rowStyleGenerator != null) { - String style = rowStyleGenerator.getStyle(r); - if (style != null && !style.isEmpty()) { - rowData.put(GridState.JSONKEY_ROWSTYLE, style); - } + String style = rowStyleGenerator.getStyle(row); + put(GridState.JSONKEY_ROWSTYLE, style, rowData); + } + + if (rowDescriptionGenerator != null) { + String description = rowDescriptionGenerator + .getDescription(row); + put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData); + } JsonObject cellStyles = Json.createObject(); JsonObject cellData = Json.createObject(); - for (Column column : getColumns()) { - Object propertyId = column.getPropertyId(); - String columnId = columnKeys.key(propertyId); + JsonObject cellDescriptions = Json.createObject(); - cellData.put(columnId, getRendererData(column, item)); + CellReference cell = new CellReference(row); - if (cellStyleGenerator != null) { - CellReference c = new CellReference(r); - c.set(propertyId); + for (Column column : getColumns()) { + cell.set(column.getPropertyId()); - String style = cellStyleGenerator.getStyle(c); - if (style != null && !style.isEmpty()) { - cellStyles.put(columnId, style); - } - } + writeData(cell, cellData); + writeStyles(cell, cellStyles); + writeDescriptions(cell, cellDescriptions); + } + + if (cellDescriptionGenerator != null + && cellDescriptions.keys().length > 0) { + rowData.put(GridState.JSONKEY_CELLDESCRIPTION, cellDescriptions); } if (cellStyleGenerator != null && cellStyles.keys().length > 0) { rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles); } + rowData.put(GridState.JSONKEY_DATA, cellData); } - private JsonValue getRendererData(Column column, Item item) { + private void writeStyles(CellReference cell, JsonObject styles) { + if (cellStyleGenerator != null) { + String style = cellStyleGenerator.getStyle(cell); + put(columnKeys.key(cell.getPropertyId()), style, styles); + } + } + + private void writeDescriptions(CellReference cell, + JsonObject descriptions) { + if (cellDescriptionGenerator != null) { + String description = cellDescriptionGenerator + .getDescription(cell); + put(columnKeys.key(cell.getPropertyId()), description, + descriptions); + } + } + + private void writeData(CellReference cell, JsonObject data) { + Column column = getColumn(cell.getPropertyId()); Converter converter = column.getConverter(); - Object propertyId = column.getPropertyId(); - Object modelValue = item.getItemProperty(propertyId).getValue(); Renderer renderer = column.getRenderer(); - return AbstractRenderer.encodeValue(modelValue, renderer, - converter, getLocale()); + Item item = cell.getItem(); + Object modelValue = item.getItemProperty(cell.getPropertyId()) + .getValue(); + + data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer + .encodeValue(modelValue, renderer, converter, getLocale())); } } @@ -3781,6 +3862,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, private CellStyleGenerator cellStyleGenerator; private RowStyleGenerator rowStyleGenerator; + private CellDescriptionGenerator cellDescriptionGenerator; + private RowDescriptionGenerator rowDescriptionGenerator; + /** * true if Grid is using the internal IndexedContainer created * in Grid() constructor, or false if the user has set their @@ -5759,6 +5843,73 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, return getRpcProxy(EditorClientRpc.class); } + /** + * Sets the {@code CellDescriptionGenerator} instance for generating + * optional descriptions (tooltips) for individual Grid cells. If a + * {@link RowDescriptionGenerator} is also set, the row description it + * generates is displayed for cells for which {@code generator} returns + * null. + * + * @param generator + * the description generator to use or {@code null} to remove a + * previously set generator if any + * + * @see #setRowDescriptionGenerator(RowDescriptionGenerator) + * + * @since + */ + public void setCellDescriptionGenerator(CellDescriptionGenerator generator) { + cellDescriptionGenerator = generator; + getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null); + datasourceExtension.refreshCache(); + } + + /** + * Returns the {@code CellDescriptionGenerator} instance used to generate + * descriptions (tooltips) for Grid cells. + * + * @return the description generator or {@code null} if no generator is set + * + * @since + */ + public CellDescriptionGenerator getCellDescriptionGenerator() { + return cellDescriptionGenerator; + } + + /** + * Sets the {@code RowDescriptionGenerator} instance for generating optional + * descriptions (tooltips) for Grid rows. If a + * {@link CellDescriptionGenerator} is also set, the row description + * generated by {@code generator} is used for cells for which the cell + * description generator returns null. + * + * + * @param generator + * the description generator to use or {@code null} to remove a + * previously set generator if any + * + * @see #setCellDescriptionGenerator(CellDescriptionGenerator) + * + * @since + */ + public void setRowDescriptionGenerator(RowDescriptionGenerator generator) { + rowDescriptionGenerator = generator; + getState().hasDescriptions = (generator != null || cellDescriptionGenerator != null); + datasourceExtension.refreshCache(); + } + + /** + * Returns the {@code RowDescriptionGenerator} instance used to generate + * descriptions (tooltips) for Grid rows + * + * @return the description generator or {@code} null if no generator is set + * + * @since + */ + public RowDescriptionGenerator getRowDescriptionGenerator() { + return rowDescriptionGenerator; + } + /** * Sets the style generator that is used for generating styles for cells * diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index c455ffe23b..0d0a5d3e9f 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -102,6 +102,20 @@ public class GridState extends TabIndexState { */ public static final String JSONKEY_CELLSTYLES = "cs"; + /** + * The key in which a row's description can be found + * + * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, String) + */ + public static final String JSONKEY_ROWDESCRIPTION = "rd"; + + /** + * The key in which a cell's description can be found + * + * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, String) + */ + public static final String JSONKEY_CELLDESCRIPTION = "cd"; + /** * The key that tells whether details are visible for the row. * @@ -160,6 +174,9 @@ public class GridState extends TabIndexState { @DelegateToWidget public boolean editorBuffered = true; + /** Whether rows and/or cells have generated descriptions (tooltips) */ + public boolean hasDescriptions; + /** The caption for the save button in the editor */ @DelegateToWidget public String editorSaveCaption = GridConstants.DEFAULT_SAVE_CAPTION; diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 3154fd2a85..33a54b1c9a 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -52,6 +52,7 @@ import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.ui.Field; import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.CellDescriptionGenerator; import com.vaadin.ui.Grid.CellReference; import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; @@ -68,6 +69,7 @@ import com.vaadin.ui.Grid.FooterCell; import com.vaadin.ui.Grid.HeaderCell; import com.vaadin.ui.Grid.HeaderRow; import com.vaadin.ui.Grid.MultiSelectionModel; +import com.vaadin.ui.Grid.RowDescriptionGenerator; import com.vaadin.ui.Grid.RowReference; import com.vaadin.ui.Grid.RowStyleGenerator; import com.vaadin.ui.Grid.SelectionMode; @@ -130,6 +132,27 @@ public class GridBasicFeatures extends AbstractComponentTest { } }; + private RowDescriptionGenerator rowDescriptionGenerator = new RowDescriptionGenerator() { + + @Override + public String getDescription(RowReference row) { + return "Row tooltip for row " + row.getItemId(); + } + }; + + private CellDescriptionGenerator cellDescriptionGenerator = new CellDescriptionGenerator() { + + @Override + public String getDescription(CellReference cell) { + if ("Column 0".equals(cell.getPropertyId())) { + return "Cell tooltip for row " + cell.getItemId() + + ", column 0"; + } else { + return null; + } + } + }; + private ItemClickListener editorOpeningItemClickListener = new ItemClickListener() { @Override @@ -629,6 +652,25 @@ public class GridBasicFeatures extends AbstractComponentTest { } }); + createBooleanAction("Row description generator", "State", false, + new Command() { + + @Override + public void execute(Grid c, Boolean value, Object data) { + c.setRowDescriptionGenerator(value ? rowDescriptionGenerator + : null); + } + }); + + createBooleanAction("Cell description generator", "State", false, + new Command() { + @Override + public void execute(Grid c, Boolean value, Object data) { + c.setCellDescriptionGenerator(value ? cellDescriptionGenerator + : null); + } + }); + LinkedHashMap frozenOptions = new LinkedHashMap(); for (int i = -1; i <= COLUMNS; i++) { frozenOptions.put(String.valueOf(i), Integer.valueOf(i)); @@ -673,6 +715,7 @@ public class GridBasicFeatures extends AbstractComponentTest { } }); + createBooleanAction("EditorOpeningItemClickListener", "State", false, new Command() { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java new file mode 100644 index 0000000000..ed712361a6 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.openqa.selenium.By; + +public class GridDescriptionGeneratorTest extends GridBasicFeaturesTest { + + @Test + public void testCellDescription() { + openTestURL(); + selectMenuPath("Component", "State", "Cell description generator"); + + getGridElement().getCell(1, 0).showTooltip(); + String tooltipText = findElement(By.className("v-tooltip-text")) + .getText(); + assertEquals("Tooltip text", "Cell tooltip for row 1, column 0", + tooltipText); + + getGridElement().getCell(1, 1).showTooltip(); + assertTrue("Tooltip should not be present in cell (1, 1) ", + findElement(By.className("v-tooltip-text")).getText().isEmpty()); + } + + @Test + public void testRowDescription() { + openTestURL(); + selectMenuPath("Component", "State", "Row description generator"); + + getGridElement().getCell(5, 3).showTooltip(); + String tooltipText = findElement(By.className("v-tooltip-text")) + .getText(); + assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText); + + getGridElement().getCell(15, 3).showTooltip(); + tooltipText = findElement(By.className("v-tooltip-text")).getText(); + assertEquals("Tooltip text", "Row tooltip for row 15", tooltipText); + } + + @Test + public void testRowAndCellDescription() { + openTestURL(); + selectMenuPath("Component", "State", "Row description generator"); + selectMenuPath("Component", "State", "Cell description generator"); + + getGridElement().getCell(5, 0).showTooltip(); + String tooltipText = findElement(By.className("v-tooltip-text")) + .getText(); + assertEquals("Tooltip text", "Cell tooltip for row 5, column 0", + tooltipText); + + getGridElement().getCell(5, 3).showTooltip(); + tooltipText = findElement(By.className("v-tooltip-text")).getText(); + assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText); + } + +} -- cgit v1.2.3 From 4a10a70fbecdd52758ebc73512974501a02d5fdd Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Mon, 20 Jul 2015 09:21:39 +0300 Subject: Fix DetailsRow communication use connector IDs (#18493) Details are now initialized when they are made visible. The old way of requesting when seen caused a lot of problems when moving stuff around. Now uses less communication, but reserves a bit extra resources due to all details components being in the hierarchy. Change-Id: I1c1163bdc306f5b86e5e0f6e2bbf2801e65c2243 --- .../vaadin/client/connectors/GridConnector.java | 395 ++++++--------------- .../client/connectors/RpcDataSourceConnector.java | 13 - .../com/vaadin/data/RpcDataProviderExtension.java | 241 ++----------- server/src/com/vaadin/ui/Grid.java | 17 +- .../com/vaadin/shared/ui/grid/GridClientRpc.java | 17 - .../com/vaadin/shared/ui/grid/GridServerRpc.java | 17 - .../components/grid/GridDetailsDetachTest.java | 24 ++ .../server/GridDetailsServerTest.java | 6 +- 8 files changed, 167 insertions(+), 563 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index ef52a429e7..4c2e8ab4e1 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -19,13 +19,13 @@ package com.vaadin.client.connectors; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; @@ -38,6 +38,7 @@ 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.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; @@ -79,10 +80,8 @@ import com.vaadin.client.widgets.Grid.FooterCell; import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderCell; import com.vaadin.client.widgets.Grid.HeaderRow; -import com.vaadin.shared.Connector; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.grid.DetailsConnectorChange; import com.vaadin.shared.ui.grid.EditorClientRpc; import com.vaadin.shared.ui.grid.EditorServerRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -420,255 +419,107 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }; - private static class CustomDetailsGenerator implements DetailsGenerator { + private class CustomDetailsGenerator implements DetailsGenerator { - private final Map indexToDetailsMap = new HashMap(); + private final Map idToDetailsMap = new HashMap(); + private final Map idToRowIndex = new HashMap(); @Override - @SuppressWarnings("boxing") public Widget getDetails(int rowIndex) { - ComponentConnector componentConnector = indexToDetailsMap - .get(rowIndex); - if (componentConnector != null) { - return componentConnector.getWidget(); - } else { - return null; - } - } - - public void setDetailsConnectorChanges( - Set changes) { - /* - * To avoid overwriting connectors while moving them about, we'll - * take all the affected connectors, first all remove those that are - * removed or moved, then we add back those that are moved or added. - */ + JsonObject row = getWidget().getDataSource().getRow(rowIndex); - /* Remove moved/removed connectors from bookkeeping */ - for (DetailsConnectorChange change : changes) { - Integer oldIndex = change.getOldIndex(); - Connector removedConnector = indexToDetailsMap.remove(oldIndex); - - Connector connector = change.getConnector(); - assert removedConnector == null || connector == null - || removedConnector.equals(connector) : "Index " - + oldIndex + " points to " + removedConnector - + " while " + connector + " was expected"; - } - - /* Add moved/added connectors to bookkeeping */ - for (DetailsConnectorChange change : changes) { - Integer newIndex = change.getNewIndex(); - ComponentConnector connector = (ComponentConnector) change - .getConnector(); - - if (connector != null) { - assert newIndex != null : "An existing connector has a missing new index."; - - ComponentConnector prevConnector = indexToDetailsMap.put( - newIndex, connector); - - assert prevConnector == null : "Connector collision at index " - + newIndex - + " between old " - + prevConnector - + " and new " + connector; - } + if (!row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) + || row.getString(GridState.JSONKEY_DETAILS_VISIBLE) + .isEmpty()) { + return null; } - } - } - - @SuppressWarnings("boxing") - private static class DetailsConnectorFetcher implements DeferredWorker { - private static final int FETCH_TIMEOUT_MS = 5000; + String id = row.getString(GridState.JSONKEY_DETAILS_VISIBLE); + ComponentConnector componentConnector = idToDetailsMap.get(id); + idToRowIndex.put(id, rowIndex); - public interface Listener { - void fetchHasBeenScheduled(int id); - - void fetchHasReturned(int id); + return componentConnector.getWidget(); } - /** A flag making sure that we don't call scheduleFinally many times. */ - private boolean fetcherHasBeenCalled = false; - - /** A rolling counter for unique values. */ - private int detailsFetchCounter = 0; - - /** A collection that tracks the amount of requests currently underway. */ - private Set pendingFetches = new HashSet(5); - - private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() { - @Override - public void execute() { - int currentFetchId = detailsFetchCounter++; - pendingFetches.add(currentFetchId); - rpc.sendDetailsComponents(currentFetchId); - fetcherHasBeenCalled = false; - - if (listener != null) { - listener.fetchHasBeenScheduled(currentFetchId); + public void updateConnectorHierarchy(List children) { + Set connectorIds = new HashSet(); + for (ServerConnector child : children) { + if (child instanceof ComponentConnector) { + connectorIds.add(child.getConnectorId()); + idToDetailsMap.put(child.getConnectorId(), + (ComponentConnector) child); } - - assert assertRequestDoesNotTimeout(currentFetchId); - } - }; - - private DetailsConnectorFetcher.Listener listener = null; - - private final GridServerRpc rpc; - - public DetailsConnectorFetcher(GridServerRpc rpc) { - assert rpc != null : "RPC was null"; - this.rpc = rpc; - } - - public void schedule() { - if (!fetcherHasBeenCalled) { - Scheduler.get().scheduleFinally(lazyDetailsFetcher); - fetcherHasBeenCalled = true; } - } - public void responseReceived(int fetchId) { - - if (fetchId < 0) { - /* Ignore negative fetchIds (they're pushed, not fetched) */ - return; + Set removedDetails = new HashSet(); + for (Entry entry : idToDetailsMap + .entrySet()) { + ComponentConnector connector = entry.getValue(); + String id = connector.getConnectorId(); + if (!connectorIds.contains(id)) { + removedDetails.add(entry.getKey()); + if (idToRowIndex.containsKey(id)) { + getWidget().setDetailsVisible(idToRowIndex.get(id), + false); + } + } } - boolean success = pendingFetches.remove(fetchId); - assert success : "Received a response with an unidentified fetch id"; - - if (listener != null) { - listener.fetchHasReturned(fetchId); + for (String id : removedDetails) { + idToDetailsMap.remove(id); + idToRowIndex.remove(id); } } - - @Override - public boolean isWorkPending() { - return fetcherHasBeenCalled || !pendingFetches.isEmpty(); - } - - private boolean assertRequestDoesNotTimeout(final int fetchId) { - /* - * This method will not be compiled without asserts enabled. This - * only makes sure that any request does not time out. - * - * TODO Should this be an explicit check? Is it worth the overhead? - */ - new Timer() { - @Override - public void run() { - assert !pendingFetches.contains(fetchId) : "Fetch id " - + fetchId + " timed out."; - } - }.schedule(FETCH_TIMEOUT_MS); - return true; - } - - public void setListener(DetailsConnectorFetcher.Listener listener) { - // if more are needed, feel free to convert this into a collection. - this.listener = listener; - } } /** - * The functionality that makes sure that the scroll position is still kept - * up-to-date even if more details are being fetched lazily. + * Class for handling scrolling issues with open details. + * + * @since + * @author Vaadin Ltd */ - private class LazyDetailsScrollAdjuster implements DeferredWorker { + private class LazyDetailsScroller implements DeferredWorker { - private static final int SCROLL_TO_END_ID = -2; - private static final int NO_SCROLL_SCHEDULED = -1; - - private class ScrollStopChecker implements DeferredWorker { - private final ScheduledCommand checkCommand = new ScheduledCommand() { - @Override - public void execute() { - isScheduled = false; - if (queuedFetches.isEmpty()) { - currentRow = NO_SCROLL_SCHEDULED; - destination = null; - } - } - }; - - private boolean isScheduled = false; - - public void schedule() { - if (isScheduled) { - return; - } - Scheduler.get().scheduleDeferred(checkCommand); - isScheduled = true; - } - - @Override - public boolean isWorkPending() { - return isScheduled; - } - } - - private DetailsConnectorFetcher.Listener fetcherListener = new DetailsConnectorFetcher.Listener() { - @Override - @SuppressWarnings("boxing") - public void fetchHasBeenScheduled(int id) { - if (currentRow != NO_SCROLL_SCHEDULED) { - queuedFetches.add(id); - } - } + /* 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 - @SuppressWarnings("boxing") - public void fetchHasReturned(int id) { - if (currentRow == NO_SCROLL_SCHEDULED - || queuedFetches.isEmpty()) { - return; - } - - queuedFetches.remove(id); - if (currentRow == SCROLL_TO_END_ID) { - getWidget().scrollToEnd(); - } else { - getWidget().scrollToRow(currentRow, destination); - } - - /* - * Schedule a deferred call whether we should stop adjusting for - * scrolling. - * - * This is done deferredly just because we can't be absolutely - * certain whether this most recent scrolling won't cascade into - * further lazy details loading (perhaps deferredly). - */ - scrollStopChecker.schedule(); + public void run() { + targetRow = -1; } }; - private int currentRow = NO_SCROLL_SCHEDULED; - private final Set queuedFetches = new HashSet(); - private final ScrollStopChecker scrollStopChecker = new ScrollStopChecker(); - private ScrollDestination destination; - - public LazyDetailsScrollAdjuster() { - detailsConnectorFetcher.setListener(fetcherListener); - } + private Integer targetRow = -1; + private ScrollDestination destination = null; - public void adjustForEnd() { - currentRow = SCROLL_TO_END_ID; + public void scrollToRow(Integer row, ScrollDestination dest) { + targetRow = row; + destination = dest; + disableScroller.schedule(DISABLE_LAZY_SCROLL_TIMEOUT); } - public void adjustFor(int row, ScrollDestination destination) { - currentRow = row; - this.destination = destination; + /** + * Inform LazyDetailsScroller that a details row has opened on a row. + * + * @since + * @param rowIndex + * index of row with details now open + */ + public void detailsOpened(int rowIndex) { + if (targetRow == rowIndex) { + getWidget().scrollToRow(targetRow, destination); + disableScroller.run(); + } } @Override public boolean isWorkPending() { - return currentRow != NO_SCROLL_SCHEDULED - || !queuedFetches.isEmpty() - || scrollStopChecker.isWorkPending(); + return disableScroller.isRunning(); } } @@ -730,39 +581,46 @@ public class GridConnector extends AbstractHasComponentsConnector implements private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator(); - private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher( - getRpcProxy(GridServerRpc.class)); - private final DetailsListener detailsListener = new DetailsListener() { @Override public void reapplyDetailsVisibility(final int rowIndex, final JsonObject row) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - if (hasDetailsOpen(row)) { - getWidget().setDetailsVisible(rowIndex, true); - detailsConnectorFetcher.schedule(); - } else { + if (hasDetailsOpen(row)) { + // Command for opening details row. + ScheduledCommand openDetails = new ScheduledCommand() { + @Override + public void execute() { + // Re-apply to force redraw. getWidget().setDetailsVisible(rowIndex, false); + getWidget().setDetailsVisible(rowIndex, true); + lazyDetailsScroller.detailsOpened(rowIndex); } + }; + + if (initialChange) { + Scheduler.get().scheduleDeferred(openDetails); + } else { + Scheduler.get().scheduleFinally(openDetails); } - }); + } else { + getWidget().setDetailsVisible(rowIndex, false); + } } private boolean hasDetailsOpen(JsonObject row) { return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) - && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE); - } - - @Override - public void closeDetails(int rowIndex) { - getWidget().setDetailsVisible(rowIndex, false); + && row.getString(GridState.JSONKEY_DETAILS_VISIBLE) != null; } }; - private final LazyDetailsScrollAdjuster lazyDetailsScrollAdjuster = new LazyDetailsScrollAdjuster(); + private final LazyDetailsScroller lazyDetailsScroller = new LazyDetailsScroller(); + + /* + * Initially details need to behave a bit differently to allow some + * escalator magic. + */ + private boolean initialChange; @Override @SuppressWarnings("unchecked") @@ -797,11 +655,13 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void scrollToEnd() { - lazyDetailsScrollAdjuster.adjustForEnd(); Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { getWidget().scrollToEnd(); + // Scrolls further if details opens. + lazyDetailsScroller.scrollToRow(dataSource.size() - 1, + ScrollDestination.END); } }); } @@ -809,11 +669,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void scrollToRow(final int row, final ScrollDestination destination) { - lazyDetailsScrollAdjuster.adjustFor(row, destination); Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { getWidget().scrollToRow(row, destination); + // Scrolls a bit further if details opens. + lazyDetailsScroller.scrollToRow(row, destination); } }); } @@ -822,51 +683,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void recalculateColumnWidths() { getWidget().recalculateColumnWidths(); } - - @Override - @SuppressWarnings("boxing") - public void setDetailsConnectorChanges( - Set connectorChanges, int fetchId) { - customDetailsGenerator - .setDetailsConnectorChanges(connectorChanges); - - List removedFirst = new ArrayList( - connectorChanges); - Collections.sort(removedFirst, - DetailsConnectorChange.REMOVED_FIRST_COMPARATOR); - - // refresh moved/added details rows - for (DetailsConnectorChange change : removedFirst) { - Integer oldIndex = change.getOldIndex(); - Integer newIndex = change.getNewIndex(); - - assert oldIndex == null || oldIndex >= 0 : "Got an " - + "invalid old index: " + oldIndex - + " (connector: " + change.getConnector() + ")"; - assert newIndex == null || newIndex >= 0 : "Got an " - + "invalid new index: " + newIndex - + " (connector: " + change.getConnector() + ")"; - - if (oldIndex != null) { - /* Close the old/removed index */ - getWidget().setDetailsVisible(oldIndex, false); - - if (change.isShouldStillBeVisible()) { - getWidget().setDetailsVisible(oldIndex, true); - } - } - - if (newIndex != null) { - /* - * Since the component was lazy loaded, we need to - * refresh the details by toggling it. - */ - getWidget().setDetailsVisible(newIndex, false); - getWidget().setDetailsVisible(newIndex, true); - } - } - detailsConnectorFetcher.responseReceived(fetchId); - } }); getWidget().addSelectionHandler(internalSelectionChangeHandler); @@ -918,17 +734,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements layout(); } - @Override - public void onUnregister() { - customDetailsGenerator.indexToDetailsMap.clear(); - - super.onUnregister(); - } - @Override public void onStateChanged(final StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); + initialChange = stateChangeEvent.isInitialStateChange(); + // Column updates if (stateChangeEvent.hasPropertyChanged("columns")) { @@ -1411,6 +1222,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void onConnectorHierarchyChange( ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { + customDetailsGenerator.updateConnectorHierarchy(getChildren()); } public String getColumnId(Grid.Column column) { @@ -1427,8 +1239,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public boolean isWorkPending() { - return detailsConnectorFetcher.isWorkPending() - || lazyDetailsScrollAdjuster.isWorkPending(); + return lazyDetailsScroller.isWorkPending(); } /** diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index 627ee74eca..c1b9f13ef4 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -64,14 +64,6 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { * @see GridState#JSONKEY_DETAILS_VISIBLE */ void reapplyDetailsVisibility(int rowIndex, JsonObject row); - - /** - * Closes details for a row. - * - * @param rowIndex - * the index of the row for which to close details - */ - void closeDetails(int rowIndex); } public class RpcDataSource extends AbstractRemoteDataSource { @@ -221,11 +213,6 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { rowData.get(i)); } } - - @Override - protected void onDropFromCache(int rowIndex) { - detailsListener.closeDetails(rowIndex); - } } private final RpcDataSource dataSource = new RpcDataSource(); diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index b3c7972b52..6f8a7e8f7b 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -25,7 +25,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; @@ -49,11 +48,9 @@ import com.vaadin.server.ClientConnector; import com.vaadin.server.KeyMapper; import com.vaadin.shared.data.DataProviderRpc; import com.vaadin.shared.data.DataRequestRpc; -import com.vaadin.shared.ui.grid.DetailsConnectorChange; import com.vaadin.shared.ui.grid.GridClientRpc; import com.vaadin.shared.ui.grid.GridState; import com.vaadin.shared.ui.grid.Range; -import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.CellReference; @@ -119,16 +116,11 @@ public class RpcDataProviderExtension extends AbstractExtension { } for (Object itemId : itemsRemoved) { - detailComponentManager.destroyDetails(itemId); itemIdToKey.remove(itemId); } for (Object itemId : itemSet) { itemIdToKey.put(itemId, getKey(itemId)); - if (visibleDetails.contains(itemId)) { - detailComponentManager.createDetails(itemId, - indexOf(itemId)); - } } } @@ -620,34 +612,11 @@ public class RpcDataProviderExtension extends AbstractExtension { private final Map visibleDetailsComponents = Maps .newHashMap(); - /** A lookup map for which row contains which details component. */ - private BiMap rowIndexToDetails = HashBiMap - .create(); - - /** - * A copy of {@link #rowIndexToDetails} from its last stable state. Used - * for creating a diff against {@link #rowIndexToDetails}. - * - * @see #getAndResetConnectorChanges() - */ - private BiMap prevRowIndexToDetails = HashBiMap - .create(); - - /** - * A set keeping track on components that have been created, but not - * attached. They should be attached at some later point in time. - *

- * This isn't strictly requried, but it's a handy explicit log. You - * could find out the same thing by taking out all the other components - * and checking whether Grid is their parent or not. - */ - private final Set unattachedComponents = Sets.newHashSet(); - /** * Keeps tabs on all the details that did not get a component during * {@link #createDetails(Object, int)}. */ - private final Map emptyDetails = Maps.newHashMap(); + private final Set emptyDetails = Sets.newHashSet(); private Grid grid; @@ -661,19 +630,16 @@ public class RpcDataProviderExtension extends AbstractExtension { * the item id for which to create the details component. * Assumed not null and that a component is not * currently present for this item previously - * @param rowIndex - * the row index for {@code itemId} * @throws IllegalStateException * if the current details generator provides a component * that was manually attached, or if the same instance has * already been provided */ - public void createDetails(Object itemId, int rowIndex) - throws IllegalStateException { + public void createDetails(Object itemId) throws IllegalStateException { assert itemId != null : "itemId was null"; - Integer newRowIndex = Integer.valueOf(rowIndex); - if (visibleDetailsComponents.containsKey(itemId)) { + if (visibleDetailsComponents.containsKey(itemId) + || emptyDetails.contains(itemId)) { // Don't overwrite existing components return; } @@ -684,58 +650,26 @@ public class RpcDataProviderExtension extends AbstractExtension { DetailsGenerator detailsGenerator = grid.getDetailsGenerator(); Component details = detailsGenerator.getDetails(rowReference); if (details != null) { - String generatorName = detailsGenerator.getClass().getName(); if (details.getParent() != null) { - throw new IllegalStateException(generatorName + String name = detailsGenerator.getClass().getName(); + throw new IllegalStateException(name + " generated a details component that already " - + "was attached. (itemId: " + itemId + ", row: " - + rowIndex + ", component: " + details); - } - - if (rowIndexToDetails.containsValue(details)) { - throw new IllegalStateException(generatorName - + " provided a details component that already " - + "exists in Grid. (itemId: " + itemId + ", row: " - + rowIndex + ", component: " + details); + + "was attached. (itemId: " + itemId + + ", component: " + details + ")"); } visibleDetailsComponents.put(itemId, details); - rowIndexToDetails.put(newRowIndex, details); - unattachedComponents.add(details); - assert !emptyDetails.containsKey(itemId) : "Bookeeping thinks " + details.setParent(grid); + grid.markAsDirty(); + + assert !emptyDetails.contains(itemId) : "Bookeeping thinks " + "itemId is empty even though we just created a " + "component for it (" + itemId + ")"; } else { - assert assertItemIdHasNotMovedAndNothingIsOverwritten(itemId, - newRowIndex); - emptyDetails.put(itemId, newRowIndex); - } - - /* - * Don't attach the components here. It's done by - * GridServerRpc.sendDetailsComponents in a separate roundtrip. - */ - } - - private boolean assertItemIdHasNotMovedAndNothingIsOverwritten( - Object itemId, Integer newRowIndex) { - - Integer oldRowIndex = emptyDetails.get(itemId); - if (!SharedUtil.equals(oldRowIndex, newRowIndex)) { - - assert !emptyDetails.containsKey(itemId) : "Unexpected " - + "change of empty details row index for itemId " - + itemId + " from " + oldRowIndex + " to " - + newRowIndex; - - assert !emptyDetails.containsValue(newRowIndex) : "Bookkeeping" - + " already had another itemId for this empty index " - + "(index: " + newRowIndex + ", new itemId: " + itemId - + ")"; + emptyDetails.add(itemId); } - return true; } /** @@ -756,8 +690,6 @@ public class RpcDataProviderExtension extends AbstractExtension { return; } - rowIndexToDetails.inverse().remove(removedComponent); - removedComponent.setParent(null); grid.markAsDirty(); } @@ -773,81 +705,12 @@ public class RpcDataProviderExtension extends AbstractExtension { public Collection getComponents() { Set components = new HashSet( visibleDetailsComponents.values()); - components.removeAll(unattachedComponents); return components; } - /** - * Gets information on how the connectors have changed. - *

- * This method only returns the changes that have been made between two - * calls of this method. I.e. Calling this method once will reset the - * state for the next state. - *

- * Used internally by the Grid object. - * - * @return information on how the connectors have changed - */ - public Set getAndResetConnectorChanges() { - Set changes = new HashSet(); - - // populate diff with added/changed - for (Entry entry : rowIndexToDetails.entrySet()) { - Component component = entry.getValue(); - assert component != null : "rowIndexToDetails contains a null component"; - - Integer newIndex = entry.getKey(); - Integer oldIndex = prevRowIndexToDetails.inverse().get( - component); - - /* - * only attach components. Detaching already happened in - * destroyDetails. - */ - if (newIndex != null && oldIndex == null) { - assert unattachedComponents.contains(component) : "unattachedComponents does not contain component for index " - + newIndex + " (" + component + ")"; - component.setParent(grid); - unattachedComponents.remove(component); - } - - if (!SharedUtil.equals(oldIndex, newIndex)) { - changes.add(new DetailsConnectorChange(component, oldIndex, - newIndex, emptyDetails.containsKey(component))); - } - } - - // populate diff with removed - for (Entry entry : prevRowIndexToDetails - .entrySet()) { - Integer oldIndex = entry.getKey(); - Component component = entry.getValue(); - Integer newIndex = rowIndexToDetails.inverse().get(component); - if (newIndex == null) { - changes.add(new DetailsConnectorChange(null, oldIndex, - null, emptyDetails.containsValue(oldIndex))); - } - } - - // reset diff map - prevRowIndexToDetails = HashBiMap.create(rowIndexToDetails); - - return changes; - } - public void refresh(Object itemId) { - Component component = visibleDetailsComponents.get(itemId); - Integer rowIndex = null; - if (component != null) { - rowIndex = rowIndexToDetails.inverse().get(component); - destroyDetails(itemId); - } else { - rowIndex = emptyDetails.remove(itemId); - } - - assert rowIndex != null : "Given itemId does not map to an " - + "existing detail row (" + itemId + ")"; - createDetails(itemId, rowIndex.intValue()); + destroyDetails(itemId); + createDetails(itemId); } void setGrid(Grid grid) { @@ -927,17 +790,13 @@ public class RpcDataProviderExtension extends AbstractExtension { listener.removeListener(); } - // Wipe clean all details. - HashSet detailItemIds = new HashSet( - detailComponentManager.visibleDetailsComponents - .keySet()); - for (Object itemId : detailItemIds) { - detailComponentManager.destroyDetails(itemId); - } - listeners.clear(); activeRowHandler.activeRange = Range.withLength(0, 0); + for (Object itemId : visibleDetails) { + detailComponentManager.destroyDetails(itemId); + } + /* Mark as dirty to push changes in beforeClientResponse */ bareItemSetTriggeredSizeChange = true; markAsDirty(); @@ -1118,7 +977,14 @@ public class RpcDataProviderExtension extends AbstractExtension { rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId)); if (visibleDetails.contains(itemId)) { - rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true); + // Double check to be sure details component exists. + detailComponentManager.createDetails(itemId); + Component detailsComponent = detailComponentManager.visibleDetailsComponents + .get(itemId); + rowObject.put( + GridState.JSONKEY_DETAILS_VISIBLE, + (detailsComponent != null ? detailsComponent + .getConnectorId() : "")); } rowReference.set(itemId); @@ -1254,15 +1120,11 @@ public class RpcDataProviderExtension extends AbstractExtension { private void internalUpdateRowData(Object itemId) { int index = container.indexOfId(itemId); - if (index >= 0) { + if (activeRowHandler.activeRange.contains(index)) { JsonValue row = getRowData(getGrid().getColumns(), itemId); JsonArray rowArray = Json.createArray(); rowArray.set(0, row); rpc.setRowData(index, rowArray); - - if (isDetailsVisible(itemId)) { - detailComponentManager.createDetails(itemId, index); - } } } @@ -1399,37 +1261,21 @@ public class RpcDataProviderExtension extends AbstractExtension { * hide */ public void setDetailsVisible(Object itemId, boolean visible) { - final boolean modified; - if (visible) { - modified = visibleDetails.add(itemId); + visibleDetails.add(itemId); /* - * We don't want to create the component here, since the component - * might be out of view, and thus we don't know where the details - * should end up on the client side. This is also a great thing to - * optimize away, so that in case a lot of things would be opened at - * once, a huge chunk of data doesn't get sent over immediately. + * This might be an issue with a huge number of open rows, but as of + * now this works in most of the cases. */ - + detailComponentManager.createDetails(itemId); } else { - modified = visibleDetails.remove(itemId); + visibleDetails.remove(itemId); - /* - * Here we can try to destroy the component no matter what. The - * component has been removed and should be detached from the - * component hierarchy. The details row will be closed on the client - * side automatically. - */ detailComponentManager.destroyDetails(itemId); } - int rowIndex = indexOf(itemId); - boolean modifiedRowIsActive = activeRowHandler.activeRange - .contains(rowIndex); - if (modified && modifiedRowIsActive) { - updateRowData(itemId); - } + updateRowData(itemId); } /** @@ -1454,18 +1300,10 @@ public class RpcDataProviderExtension extends AbstractExtension { public void refreshDetails() { for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { detailComponentManager.refresh(itemId); + updateRowData(itemId); } } - private int indexOf(Object itemId) { - /* - * It would be great if we could optimize this method away, since the - * normal usage of Grid doesn't need any indices to be known. It was - * already optimized away once, maybe we can do away with these as well. - */ - return container.indexOfId(itemId); - } - /** * Gets the detail component manager for this data provider * @@ -1475,13 +1313,4 @@ public class RpcDataProviderExtension extends AbstractExtension { public DetailComponentManager getDetailComponentManager() { return detailComponentManager; } - - @Override - public void detach() { - for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { - detailComponentManager.destroyDetails(itemId); - } - - super.detach(); - } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index e9469c5bca..ea973bb3ba 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -3281,7 +3281,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * currently extends the AbstractExtension superclass, but this fact should * be regarded as an implementation detail and subject to change in a future * major or minor Vaadin revision. - * + * * @param * the type this renderer knows how to present */ @@ -3354,7 +3354,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * is desired. For instance, a {@code Renderer} could first turn a * date value into a formatted string and return * {@code encode(dateString, String.class)}. - * + * * @param value * the value to be encoded * @param type @@ -3369,7 +3369,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * An abstract base class for server-side Grid extensions. - * + * * @since 7.5 */ public static abstract class AbstractGridExtension extends @@ -3384,7 +3384,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Constructs a new Grid extension and extends given Grid. - * + * * @param grid * a grid instance */ @@ -3856,13 +3856,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, userOriginated); } } - - @Override - public void sendDetailsComponents(int fetchId) { - getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( - detailComponentManager.getAndResetConnectorChanges(), - fetchId); - } }); registerRpc(new EditorServerRpc() { @@ -6063,8 +6056,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, this.detailsGenerator = detailsGenerator; datasourceExtension.refreshDetails(); - getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( - detailComponentManager.getAndResetConnectorChanges(), -1); } /** diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 3c6d993482..ac1b1d5a78 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -15,8 +15,6 @@ */ package com.vaadin.shared.ui.grid; -import java.util.Set; - import com.vaadin.shared.communication.ClientRpc; /** @@ -57,19 +55,4 @@ public interface GridClientRpc extends ClientRpc { * Command client Grid to recalculate column widths. */ public void recalculateColumnWidths(); - - /** - * Informs the GridConnector on how the indexing of details connectors has - * changed. - * - * @since 7.5.0 - * @param connectorChanges - * the indexing changes of details connectors - * @param fetchId - * the id of the request for fetching the changes. A negative - * number indicates a push (not requested by the client side) - */ - public void setDetailsConnectorChanges( - Set connectorChanges, int fetchId); - } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index dca55c11c4..5c91f2b746 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -60,23 +60,6 @@ public interface GridServerRpc extends ServerRpc { void columnsReordered(List newColumnOrder, List oldColumnOrder); - /** - * This is a trigger for Grid to send whatever has changed regarding the - * details components. - *

- * The components can't be sent eagerly, since they are generated as a side - * effect in - * {@link com.vaadin.data.RpcDataProviderExtension#beforeClientResponse(boolean)} - * , and that is too late to change the hierarchy. So we need this - * round-trip to work around that limitation. - * - * @since 7.5.0 - * @param fetchId - * an unique identifier for the request - * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) - */ - void sendDetailsComponents(int fetchId); - /** * Informs the server that the column's visibility has been changed. * diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java index fc79fd1b68..7406daeacd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java @@ -70,4 +70,28 @@ public class GridDetailsDetachTest extends MultiBrowserTest { Assert.assertEquals("Spacer content not visible", "Extra data for Bean 5", spacers.get(1).getText()); } + + @Test + public void testDetachAndImmediateReattach() { + setDebug(true); + openTestURL(); + + $(GridElement.class).first().getCell(3, 0).click(); + $(GridElement.class).first().getCell(5, 0).click(); + + assertNoErrorNotifications(); + + // Detach and Re-attach Grid + $(ButtonElement.class).get(1).click(); + + assertNoErrorNotifications(); + + List spacers = findElements(By.className("v-grid-spacer")); + Assert.assertEquals("Not enough spacers in DOM", 2, spacers.size()); + Assert.assertEquals("Spacer content not visible", + "Extra data for Bean 3", spacers.get(0).getText()); + Assert.assertEquals("Spacer content not visible", + "Extra data for Bean 5", spacers.get(1).getText()); + } + } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java index 4ea64073f3..326dbcd55f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; @@ -196,14 +195,11 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { assertEquals("Two", getGridElement().getDetails(0).getText()); } - @Ignore("This use case is not currently supported by Grid. If the detail " - + "is out of view, the component is detached from the UI and a " - + "new instance is generated when scrolled back. Support will " - + "maybe be incorporated at a later time") @Test public void hierarchyChangesWorkInDetailsWhileOutOfView() { selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + assertEquals("One", getGridElement().getDetails(0).getText()); scrollGridVerticallyTo(10000); selectMenuPath(CHANGE_HIERARCHY); scrollGridVerticallyTo(0); -- cgit v1.2.3 From bebb7efeaee4f02ac7d844cb0b11524d242eccba Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 30 Jul 2015 13:01:56 +0300 Subject: Detect Edge correctly (#18537) Change-Id: I6aa7e7b7498ff85489843e52bd351e54c4ba70f9 --- client/src/com/vaadin/client/BrowserInfo.java | 16 ++++++++++++++ .../client/VBrowserDetailsUserAgentParserTest.java | 25 ++++++++++++++++++++++ server/src/com/vaadin/server/WebBrowser.java | 14 ++++++++++++ shared/src/com/vaadin/shared/VBrowserDetails.java | 25 ++++++++++++++++++++++ 4 files changed, 80 insertions(+) (limited to 'shared') diff --git a/client/src/com/vaadin/client/BrowserInfo.java b/client/src/com/vaadin/client/BrowserInfo.java index 8b274623c1..8dcefddcf5 100644 --- a/client/src/com/vaadin/client/BrowserInfo.java +++ b/client/src/com/vaadin/client/BrowserInfo.java @@ -30,6 +30,7 @@ public class BrowserInfo { private static final String BROWSER_OPERA = "op"; private static final String BROWSER_IE = "ie"; + private static final String BROWSER_EDGE = "edge"; private static final String BROWSER_FIREFOX = "ff"; private static final String BROWSER_SAFARI = "sa"; @@ -171,6 +172,13 @@ public class BrowserInfo { minorVersionClass = majorVersionClass + browserDetails.getBrowserMinorVersion(); browserEngineClass = ENGINE_TRIDENT; + } else if (browserDetails.isEdge()) { + browserIdentifier = BROWSER_EDGE; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ""; } else if (browserDetails.isOpera()) { browserIdentifier = BROWSER_OPERA; majorVersionClass = browserIdentifier @@ -225,6 +233,10 @@ public class BrowserInfo { return browserDetails.isIE(); } + public boolean isEdge() { + return browserDetails.isEdge(); + } + public boolean isFirefox() { return browserDetails.isFirefox(); } @@ -245,6 +257,10 @@ public class BrowserInfo { return isIE() && getBrowserMajorVersion() == 10; } + public boolean isIE11() { + return isIE() && getBrowserMajorVersion() == 11; + } + public boolean isChrome() { return browserDetails.isChrome(); } diff --git a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java index 62b727e5f5..24bf9b6558 100644 --- a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java +++ b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java @@ -56,6 +56,8 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { private static final String ANDROID_MOTOROLA_3_0 = "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13"; private static final String ANDROID_GALAXY_NEXUS_4_0_4_CHROME = "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"; + private static final String EDGE_WINDOWS_10 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"; + public void testSafari3() { VBrowserDetails bd = new VBrowserDetails(SAFARI3_WINDOWS); assertWebKit(bd); @@ -423,6 +425,14 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertWindows(bd, true); } + public void testEdgeWindows10() { + VBrowserDetails bd = new VBrowserDetails(EDGE_WINDOWS_10); + assertEdge(bd); + assertBrowserMajorVersion(bd, 12); + assertBrowserMinorVersion(bd, 10240); + assertWindows(bd, false); + } + /* * Helper methods below */ @@ -484,6 +494,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertChrome(VBrowserDetails browserDetails) { @@ -493,6 +504,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertIE(VBrowserDetails browserDetails) { @@ -502,6 +514,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertTrue(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertOpera(VBrowserDetails browserDetails) { @@ -511,6 +524,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertTrue(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertSafari(VBrowserDetails browserDetails) { @@ -520,6 +534,17 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertTrue(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertEdge(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertTrue(browserDetails.isEdge()); } private void assertMacOSX(VBrowserDetails browserDetails) { diff --git a/server/src/com/vaadin/server/WebBrowser.java b/server/src/com/vaadin/server/WebBrowser.java index 66018b02f2..9bf30cb3db 100644 --- a/server/src/com/vaadin/server/WebBrowser.java +++ b/server/src/com/vaadin/server/WebBrowser.java @@ -125,6 +125,20 @@ public class WebBrowser implements Serializable { return browserDetails.isIE(); } + /** + * Tests whether the user is using Edge. + * + * @return true if the user is using Edge, false if the user is not using + * Edge or if no information on the browser is present + */ + public boolean isEdge() { + if (browserDetails == null) { + return false; + } + + return browserDetails.isEdge(); + } + /** * Tests whether the user is using Safari. * diff --git a/shared/src/com/vaadin/shared/VBrowserDetails.java b/shared/src/com/vaadin/shared/VBrowserDetails.java index 561b6c76d0..d0de8ffb9f 100644 --- a/shared/src/com/vaadin/shared/VBrowserDetails.java +++ b/shared/src/com/vaadin/shared/VBrowserDetails.java @@ -41,6 +41,7 @@ public class VBrowserDetails implements Serializable { private boolean isFirefox = false; private boolean isOpera = false; private boolean isIE = false; + private boolean isEdge = false; private boolean isPhantomJS = false; private boolean isWindowsPhone; @@ -88,6 +89,16 @@ public class VBrowserDetails implements Serializable { isSafari = !isChrome && !isIE && userAgent.indexOf("safari") != -1; isFirefox = userAgent.indexOf(" firefox/") != -1; isPhantomJS = userAgent.indexOf("phantomjs/") != -1; + if (userAgent.indexOf(" edge/") != -1) { + isEdge = true; + isChrome = false; + isOpera = false; + isIE = false; + isSafari = false; + isFirefox = false; + isWebKit = false; + isGecko = false; + } // chromeframe isChromeFrameCapable = userAgent.indexOf("chromeframe") != -1; @@ -115,6 +126,8 @@ public class VBrowserDetails implements Serializable { tmp = tmp.replaceFirst("([0-9]+\\.[0-9]+).*", "$1"); browserEngineVersion = Float.parseFloat(tmp); } + } else if (isEdge) { + browserEngineVersion = 0; } } catch (Exception e) { // Browser engine version parsing failed @@ -158,6 +171,9 @@ public class VBrowserDetails implements Serializable { i = userAgent.indexOf("opera/") + 6; } parseVersionString(safeSubstring(userAgent, i, i + 5)); + } else if (isEdge) { + int i = userAgent.indexOf(" edge/") + 6; + parseVersionString(safeSubstring(userAgent, i, i + 8)); } } catch (Exception e) { // Browser version parsing failed @@ -372,6 +388,15 @@ public class VBrowserDetails implements Serializable { return isIE; } + /** + * Tests if the browser is Edge. + * + * @return true if it is Edge, false otherwise + */ + public boolean isEdge() { + return isEdge; + } + /** * Tests if the browser is PhantomJS. * -- cgit v1.2.3 From 3c7eab0d5810a16a31778b22af22e8a34251db1a Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Thu, 9 Jul 2015 23:23:35 +0300 Subject: Update Select all -CheckBox from server and partial selections (#17590) Change-Id: I8f4986455029fc3b997ec5fee8916fa118a487ca --- .../vaadin/client/connectors/GridConnector.java | 21 ++++++ client/src/com/vaadin/client/widgets/Grid.java | 78 +++++++++++++--------- server/src/com/vaadin/ui/Grid.java | 17 +++++ .../com/vaadin/shared/ui/grid/GridClientRpc.java | 11 +++ .../grid/basicfeatures/GridBasicFeatures.java | 20 ++++++ .../basicfeatures/server/GridSelectionTest.java | 40 +++++++++++ 6 files changed, 155 insertions(+), 32 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index a99b227abe..15acbc0d5a 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -33,6 +33,7 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; @@ -681,6 +682,26 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void recalculateColumnWidths() { getWidget().recalculateColumnWidths(); } + + @Override + public void setSelectAll(boolean allSelected) { + if (selectionModel instanceof SelectionModel.Multi + && selectionModel.getSelectionColumnRenderer() != null) { + final Widget widget; + try { + HeaderRow defaultHeaderRow = getWidget() + .getDefaultHeaderRow(); + if (defaultHeaderRow != null) { + widget = defaultHeaderRow.getCell( + getWidget().getColumn(0)).getWidget(); + ((CheckBox) widget).setValue(allSelected, false); + } + } catch (Exception e) { + getLogger().warning( + "Problems finding select all checkbox."); + } + } + } }); getWidget().addSelectionHandler(internalSelectionChangeHandler); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 377943ed61..8db6e9a55a 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2377,6 +2377,7 @@ public class Grid extends ResizeComposite implements private boolean initDone = false; private boolean selected = false; + private CheckBox selectAllCheckBox; SelectionColumn(final Renderer selectColumnRenderer) { super(selectColumnRenderer); @@ -2401,41 +2402,55 @@ public class Grid extends ResizeComposite implements * exist. */ final SelectionModel.Multi model = (Multi) getSelectionModel(); - final CheckBox checkBox = GWT.create(CheckBox.class); - checkBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - if (event.getValue()) { - fireEvent(new SelectAllEvent(model)); - selected = true; - } else { - model.deselectAll(); - selected = false; - } - } - }); - checkBox.setValue(selected); - selectionCell.setWidget(checkBox); - // Select all with space when "select all" cell is active - addHeaderKeyUpHandler(new HeaderKeyUpHandler() { - @Override - public void onKeyUp(GridKeyUpEvent event) { - if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { - return; - } - HeaderRow targetHeaderRow = getHeader().getRow( - event.getFocusedCell().getRowIndex()); - if (!targetHeaderRow.isDefault()) { - return; + if (selectAllCheckBox == null) { + selectAllCheckBox = GWT.create(CheckBox.class); + selectAllCheckBox + .addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange( + ValueChangeEvent event) { + if (event.getValue()) { + fireEvent(new SelectAllEvent(model)); + selected = true; + } else { + model.deselectAll(); + selected = false; + } + } + }); + selectAllCheckBox.setValue(selected); + + // Select all with space when "select all" cell is active + addHeaderKeyUpHandler(new HeaderKeyUpHandler() { + @Override + public void onKeyUp(GridKeyUpEvent event) { + if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { + return; + } + HeaderRow targetHeaderRow = getHeader().getRow( + event.getFocusedCell().getRowIndex()); + if (!targetHeaderRow.isDefault()) { + return; + } + if (event.getFocusedCell().getColumn() == SelectionColumn.this) { + // Send events to ensure state is updated + selectAllCheckBox.setValue( + !selectAllCheckBox.getValue(), true); + } } - if (event.getFocusedCell().getColumn() == SelectionColumn.this) { - // Send events to ensure row selection state is - // updated - checkBox.setValue(!checkBox.getValue(), true); + }); + } else { + for (HeaderRow row : header.getRows()) { + if (row.getCell(this).getType() == GridStaticCellType.WIDGET) { + // Detach from old header. + row.getCell(this).setText(""); } } - }); + } + + selectionCell.setWidget(selectAllCheckBox); } @Override @@ -2497,7 +2512,6 @@ public class Grid extends ResizeComposite implements super.setEditable(editable); return this; } - } /** diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index ea973bb3ba..3e4a78db9d 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -1068,6 +1068,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private int selectionLimit = DEFAULT_MAX_SELECTIONS; + private boolean allSelected; + @Override public boolean select(final Object... itemIds) throws IllegalArgumentException { @@ -1113,6 +1115,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } fireSelectionEvent(oldSelection, selection); } + + updateAllSelectedState(); + return selectionWillChange; } @@ -1178,6 +1183,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, selection.removeAll(itemIds); fireSelectionEvent(oldSelection, selection); } + + updateAllSelectedState(); + return hasCommonElements; } @@ -1258,6 +1266,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, fireSelectionEvent(oldSelection, selection); } + updateAllSelectedState(); + return changed; } @@ -1271,6 +1281,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier, "Vararg array of itemIds may not be null"); } } + + private void updateAllSelectedState() { + if (allSelected != selection.size() >= selectionLimit) { + allSelected = selection.size() >= selectionLimit; + grid.getRpcProxy(GridClientRpc.class).setSelectAll(allSelected); + } + } } /** diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index ac1b1d5a78..cf4e95d078 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -55,4 +55,15 @@ public interface GridClientRpc extends ClientRpc { * Command client Grid to recalculate column widths. */ public void recalculateColumnWidths(); + + /** + * Informs the Grid that all items have been selected or not selected on the + * server side. + * + * @since + * @param allSelected + * true to check the select all checkbox, + * false to uncheck it. + */ + public void setSelectAll(boolean allSelected); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index ef51cdf446..6511866897 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -683,6 +683,26 @@ public class GridBasicFeatures extends AbstractComponentTest { c.setColumnReorderingAllowed(value); } }); + + createClickAction("Select all", "State", new Command() { + @Override + public void execute(Grid c, String value, Object data) { + SelectionModel selectionModel = c.getSelectionModel(); + if (selectionModel instanceof SelectionModel.Multi) { + ((SelectionModel.Multi) selectionModel).selectAll(); + } + } + }, null); + + createClickAction("Select none", "State", new Command() { + @Override + public void execute(Grid c, String value, Object data) { + SelectionModel selectionModel = c.getSelectionModel(); + if (selectionModel instanceof SelectionModel.Multi) { + ((SelectionModel.Multi) selectionModel).deselectAll(); + } + } + }, null); } protected void createHeaderActions() { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java index 9953bbcae0..1f3085d273 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java @@ -20,8 +20,10 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; import org.openqa.selenium.Keys; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.ExpectedCondition; import com.vaadin.testbench.By; import com.vaadin.testbench.elements.GridElement; @@ -335,6 +337,44 @@ public class GridSelectionTest extends GridBasicFeaturesTest { getRow(5).isSelected()); } + @Test + public void testServerSideSelectTogglesSelectAllCheckBox() { + openTestURL(); + + setSelectionModelMulti(); + GridCellElement header = getGridElement().getHeaderCell(0, 0); + + WebElement selectAll = header.findElement(By.tagName("input")); + + selectMenuPath("Component", "State", "Select all"); + waitUntilCheckBoxValue(selectAll, true); + assertTrue("Select all CheckBox wasn't selected as expected", + selectAll.isSelected()); + + selectMenuPath("Component", "State", "Select none"); + waitUntilCheckBoxValue(selectAll, false); + assertFalse("Select all CheckBox was selected unexpectedly", + selectAll.isSelected()); + + selectMenuPath("Component", "State", "Select all"); + waitUntilCheckBoxValue(selectAll, true); + getGridElement().getCell(5, 0).click(); + waitUntilCheckBoxValue(selectAll, false); + assertFalse("Select all CheckBox was selected unexpectedly", + selectAll.isSelected()); + } + + private void waitUntilCheckBoxValue(final WebElement checkBoxElememnt, + final boolean expectedValue) { + waitUntil(new ExpectedCondition() { + @Override + public Boolean apply(WebDriver input) { + return expectedValue ? checkBoxElememnt.isSelected() + : !checkBoxElememnt.isSelected(); + } + }, 5); + } + private void setSelectionModelMulti() { selectMenuPath("Component", "State", "Selection mode", "multi"); } -- cgit v1.2.3 From 55dc0ade6455056ce2bd910b34ddf9e242845d24 Mon Sep 17 00:00:00 2001 From: patrik Date: Wed, 5 Aug 2015 13:55:45 +0300 Subject: Add @since for change 11526 ticket #17590 Change-Id: I2154167c39384a122b06bbdaaac6c161e3fffd2f --- client/src/com/vaadin/client/ComputedStyle.java | 8 ++++---- shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/ComputedStyle.java b/client/src/com/vaadin/client/ComputedStyle.java index 1391a84bfe..cd90e0e78b 100644 --- a/client/src/com/vaadin/client/ComputedStyle.java +++ b/client/src/com/vaadin/client/ComputedStyle.java @@ -283,7 +283,7 @@ public class ComputedStyle { /** * Returns the sum of the top and bottom border width * - * @since + * @since 7.5.3 * @return the sum of the top and bottom border */ public double getBorderHeight() { @@ -296,7 +296,7 @@ public class ComputedStyle { /** * Returns the sum of the left and right border width * - * @since + * @since 7.5.3 * @return the sum of the left and right border */ public double getBorderWidth() { @@ -309,7 +309,7 @@ public class ComputedStyle { /** * Returns the sum of the top and bottom padding * - * @since + * @since 7.5.3 * @return the sum of the top and bottom padding */ public double getPaddingHeight() { @@ -322,7 +322,7 @@ public class ComputedStyle { /** * Returns the sum of the top and bottom padding * - * @since + * @since 7.5.3 * @return the sum of the left and right padding */ public double getPaddingWidth() { diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index cf4e95d078..8711a757a1 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -60,7 +60,7 @@ public interface GridClientRpc extends ClientRpc { * Informs the Grid that all items have been selected or not selected on the * server side. * - * @since + * @since 7.5.3 * @param allSelected * true to check the select all checkbox, * false to uncheck it. -- cgit v1.2.3 From 15ad8bccfc9073cdf1e587f7f7dd6e1f3f27c43f Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Wed, 22 Jul 2015 14:50:26 +0300 Subject: Fix RpcDataProviderExtension to not rely on item indices (#18503) Change-Id: I68a77bee4ef8e7a859f3a3c143e6f5922decf025 --- .../client/connectors/RpcDataSourceConnector.java | 27 ++ .../client/data/AbstractRemoteDataSource.java | 18 +- .../com/vaadin/data/RpcDataProviderExtension.java | 352 +++++---------------- .../com/vaadin/shared/data/DataProviderRpc.java | 12 + .../src/com/vaadin/shared/data/DataRequestRpc.java | 14 +- .../tests/components/grid/GridDetailsDetach.java | 1 - 6 files changed, 152 insertions(+), 272 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index c1b9f13ef4..5680b09cef 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -17,6 +17,7 @@ package com.vaadin.client.connectors; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.vaadin.client.ServerConnector; @@ -96,6 +97,11 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { public void resetDataAndSize(int size) { RpcDataSource.this.resetDataAndSize(size); } + + @Override + public void updateRowData(JsonObject row) { + RpcDataSource.this.updateRowData(row); + } }); } @@ -213,6 +219,27 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { rowData.get(i)); } } + + /** + * Updates row data based on row key. + * + * @since + * @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) { + super.onDropFromCache(rowIndex); + + rpcProxy.dropRow(getRowKey(getRow(rowIndex))); + } } private final RpcDataSource dataSource = new RpcDataSource(); diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 459127c9b4..c8910f8699 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -330,16 +330,18 @@ public abstract class AbstractRemoteDataSource implements DataSource { private void dropFromCache(Range range) { for (int i = range.getStart(); i < range.getEnd(); i++) { + // Called before dropping from cache, so we can actually do + // something with the data before the drop. + onDropFromCache(i); + T removed = indexToRowMap.remove(Integer.valueOf(i)); keyToIndexMap.remove(getRowKey(removed)); - - onDropFromCache(i); } } /** - * A hook that can be overridden to do something whenever a row is dropped - * from the cache. + * A hook that can be overridden to do something whenever a row is about to + * be dropped from the cache. * * @since 7.5.0 * @param rowIndex @@ -732,4 +734,12 @@ public abstract class AbstractRemoteDataSource implements DataSource { dataChangeHandler.resetDataAndSize(newSize); } } + + protected int indexOfKey(Object rowKey) { + if (!keyToIndexMap.containsKey(rowKey)) { + return -1; + } else { + return keyToIndexMap.get(rowKey); + } + } } diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 98394c45df..848873098d 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -100,31 +101,6 @@ public class RpcDataProviderExtension extends AbstractExtension { // private implementation } - /** - * Sets the currently active rows. This will purge any unpinned rows - * from cache. - * - * @param itemIds - * collection of itemIds to map to row keys - */ - void setActiveRows(Collection itemIds) { - Set itemSet = new HashSet(itemIds); - Set itemsRemoved = new HashSet(); - for (Object itemId : itemIdToKey.keySet()) { - if (!itemSet.contains(itemId) && !isPinned(itemId)) { - itemsRemoved.add(itemId); - } - } - - for (Object itemId : itemsRemoved) { - itemIdToKey.remove(itemId); - } - - for (Object itemId : itemSet) { - itemIdToKey.put(itemId, getKey(itemId)); - } - } - private String nextKey() { return String.valueOf(rollingIndex++); } @@ -273,246 +249,91 @@ public class RpcDataProviderExtension extends AbstractExtension { public boolean isPinned(Object itemId) { return pinnedItemIds.contains(itemId); } - } - /** - * A helper class that handles the client-side Escalator logic relating to - * making sure that whatever is currently visible to the user, is properly - * initialized and otherwise handled on the server side (as far as - * required). - *

- * This bookeeping includes, but is not limited to: - *

    - *
  • listening to the currently visible {@link com.vaadin.data.Property - * Properties'} value changes on the server side and sending those back to - * the client; and - *
  • attaching and detaching {@link com.vaadin.ui.Component Components} - * from the Vaadin Component hierarchy. - *
- */ - private class ActiveRowHandler implements Serializable { /** - * A map from index to the value change listener used for all of column - * properties - */ - private final Map valueChangeListeners = new HashMap(); - - /** - * The currently active range. Practically, it's the range of row - * indices being cached currently. - */ - private Range activeRange = Range.withLength(0, 0); - - /** - * A hook for making sure that appropriate data is "active". All other - * rows should be "inactive". - *

- * "Active" can mean different things in different contexts. For - * example, only the Properties in the active range need - * ValueChangeListeners. Also, whenever a row with a Component becomes - * active, it needs to be attached (and conversely, when inactive, it - * needs to be detached). + * Removes all inactive item id to key mapping from the key mapper. * - * @param firstActiveRow - * the first active row - * @param activeRowCount - * the number of active rows + * @since */ - public void setActiveRows(Range newActiveRange) { - - // TODO [[Components]] attach and detach components - - /*- - * Example - * - * New Range: [3, 4, 5, 6, 7] - * Old Range: [1, 2, 3, 4, 5] - * Result: [1, 2][3, 4, 5] [] - */ - final Range[] depractionPartition = activeRange - .partitionWith(newActiveRange); - removeValueChangeListeners(depractionPartition[0]); - removeValueChangeListeners(depractionPartition[2]); - - /*- - * Example - * - * Old Range: [1, 2, 3, 4, 5] - * New Range: [3, 4, 5, 6, 7] - * Result: [] [3, 4, 5][6, 7] - */ - final Range[] activationPartition = newActiveRange - .partitionWith(activeRange); - addValueChangeListeners(activationPartition[0]); - addValueChangeListeners(activationPartition[2]); - - activeRange = newActiveRange; - - assert valueChangeListeners.size() == newActiveRange.length() : "Value change listeners not set up correctly!"; - } - - private void addValueChangeListeners(Range range) { - for (Integer i = range.getStart(); i < range.getEnd(); i++) { - - final Object itemId = container.getIdByIndex(i); - final Item item = container.getItem(itemId); - - assert valueChangeListeners.get(i) == null : "Overwriting existing listener"; - - GridValueChangeListener listener = new GridValueChangeListener( - itemId, item); - valueChangeListeners.put(i, listener); + public void dropInactiveItems() { + Collection active = activeItemHandler.getActiveItemIds(); + Iterator itemIter = itemIdToKey.keySet().iterator(); + while (itemIter.hasNext()) { + Object itemId = itemIter.next(); + if (!active.contains(itemId) && !isPinned(itemId)) { + itemIter.remove(); + } } } + } - private void removeValueChangeListeners(Range range) { - for (Integer i = range.getStart(); i < range.getEnd(); i++) { - final GridValueChangeListener listener = valueChangeListeners - .remove(i); + /** + * Class for keeping track of current items and ValueChangeListeners. + * + * @since + */ + private class ActiveItemHandler implements Serializable { - assert listener != null : "Trying to remove nonexisting listener"; - - listener.removeListener(); - } - } + private final Map activeItemMap = new HashMap(); + private final Set droppedItems = new HashSet(); /** - * Manages removed columns in active rows. - *

- * This method does not send data again to the client. + * Registers ValueChangeListeners for given items ids. * - * @param removedColumns - * the columns that have been removed from the grid + * @param itemIds + * collection of new active item ids */ - public void columnsRemoved(Collection removedColumns) { - if (removedColumns.isEmpty()) { - return; + public void addActiveItems(Collection itemIds) { + for (Object itemId : itemIds) { + if (!activeItemMap.containsKey(itemId)) { + activeItemMap.put(itemId, new GridValueChangeListener( + itemId, container.getItem(itemId))); + } } - for (GridValueChangeListener listener : valueChangeListeners - .values()) { - listener.removeColumns(removedColumns); - } + // Remove still active rows that were "dropped" + droppedItems.removeAll(itemIds); + dropListeners(droppedItems); + droppedItems.clear(); } /** - * Manages added columns in active rows. - *

- * This method sends the data for the changed rows to client side. + * Marks given item id as dropped. Dropped items are cleared when adding + * new active items. * - * @param addedColumns - * the columns that have been added to the grid + * @param itemId + * dropped item id */ - public void columnsAdded(Collection addedColumns) { - if (addedColumns.isEmpty()) { - return; + public void dropActiveItem(Object itemId) { + if (activeItemMap.containsKey(itemId)) { + droppedItems.add(itemId); } + } - for (GridValueChangeListener listener : valueChangeListeners - .values()) { - listener.addColumns(addedColumns); + private void dropListeners(Collection itemIds) { + for (Object itemId : droppedItems) { + assert activeItemMap.containsKey(itemId) : "Item ID should exist in the activeItemMap"; + + activeItemMap.remove(itemId).removeListener(); } } /** - * Handles the insertion of rows. - *

- * This method's responsibilities are to: - *

    - *
  • shift the internal bookkeeping by count if the - * insertion happens above currently active range - *
  • ignore rows inserted below the currently active range - *
  • shift (and deactivate) rows pushed out of view - *
  • activate rows that are inserted in the current viewport - *
+ * Gets a collection copy of currently active item ids. * - * @param firstIndex - * the index of the first inserted rows - * @param count - * the number of rows inserted at firstIndex + * @return collection of item ids */ - public void insertRows(int firstIndex, int count) { - if (firstIndex < activeRange.getStart()) { - moveListeners(activeRange, count); - activeRange = activeRange.offsetBy(count); - } else if (firstIndex < activeRange.getEnd()) { - int end = activeRange.getEnd(); - // Move rows from first added index by count - Range movedRange = Range.between(firstIndex, end); - moveListeners(movedRange, count); - // Remove excess listeners from extra rows - removeValueChangeListeners(Range.withLength(end, count)); - // Add listeners for new rows - final Range freshRange = Range.withLength(firstIndex, count); - addValueChangeListeners(freshRange); - } else { - // out of view, noop - } + public Collection getActiveItemIds() { + return new HashSet(activeItemMap.keySet()); } /** - * Handles the removal of rows. - *

- * This method's responsibilities are to: - *

    - *
  • shift the internal bookkeeping by count if the - * removal happens above currently active range - *
  • ignore rows removed below the currently active range - *
+ * Gets a collection copy of currently active ValueChangeListeners. * - * @param firstIndex - * the index of the first removed rows - * @param count - * the number of rows removed at firstIndex + * @return collection of value change listeners */ - public void removeRows(int firstIndex, int count) { - Range removed = Range.withLength(firstIndex, count); - if (removed.intersects(activeRange)) { - final Range[] deprecated = activeRange.partitionWith(removed); - // Remove the listeners that are no longer existing - removeValueChangeListeners(deprecated[1]); - - // Move remaining listeners to fill the listener map correctly - moveListeners(deprecated[2], -deprecated[1].length()); - activeRange = Range.withLength(activeRange.getStart(), - activeRange.length() - deprecated[1].length()); - - } else { - if (removed.getEnd() < activeRange.getStart()) { - /* firstIndex < lastIndex < start */ - moveListeners(activeRange, -count); - activeRange = activeRange.offsetBy(-count); - } - /* else: end <= firstIndex, no need to do anything */ - } - } - - /** - * Moves value change listeners in map with given index range by count - */ - private void moveListeners(Range movedRange, int diff) { - if (diff < 0) { - for (Integer i = movedRange.getStart(); i < movedRange.getEnd(); ++i) { - moveListener(i, i + diff); - } - } else if (diff > 0) { - for (Integer i = movedRange.getEnd() - 1; i >= movedRange - .getStart(); --i) { - moveListener(i, i + diff); - } - } else { - // diff == 0 should not happen. If it does, should be no-op - return; - } - } - - private void moveListener(Integer oldIndex, Integer newIndex) { - assert valueChangeListeners.get(newIndex) == null : "Overwriting existing listener"; - - GridValueChangeListener listener = valueChangeListeners - .remove(oldIndex); - assert listener != null : "Moving nonexisting listener."; - valueChangeListeners.put(newIndex, listener); + public Collection getValueChangeListeners() { + return new HashSet(activeItemMap.values()); } } @@ -724,8 +545,6 @@ public class RpcDataProviderExtension extends AbstractExtension { private final Indexed container; - private final ActiveRowHandler activeRowHandler = new ActiveRowHandler(); - private DataProviderRpc rpc; private final ItemSetChangeListener itemListener = new ItemSetChangeListener() { @@ -786,14 +605,6 @@ public class RpcDataProviderExtension extends AbstractExtension { * taking all the corner cases into account. */ - Map listeners = activeRowHandler.valueChangeListeners; - for (GridValueChangeListener listener : listeners.values()) { - listener.removeListener(); - } - - listeners.clear(); - activeRowHandler.activeRange = Range.withLength(0, 0); - for (Object itemId : visibleDetails) { detailComponentManager.destroyDetails(itemId); } @@ -834,6 +645,7 @@ public class RpcDataProviderExtension extends AbstractExtension { private Set visibleDetails = new HashSet(); private final DetailComponentManager detailComponentManager = new DetailComponentManager(); + private final ActiveItemHandler activeItemHandler = new ActiveItemHandler(); /** * Creates a new data provider using the given container. @@ -849,7 +661,6 @@ public class RpcDataProviderExtension extends AbstractExtension { @Override public void requestRows(int firstRow, int numberOfRows, int firstCachedRowIndex, int cacheSize) { - pushRowData(firstRow, numberOfRows, firstCachedRowIndex, cacheSize); } @@ -867,6 +678,11 @@ public class RpcDataProviderExtension extends AbstractExtension { keyMapper.unpin(itemId); } } + + @Override + public void dropRow(String rowKey) { + activeItemHandler.dropActiveItem(keyMapper.getItemId(rowKey)); + } }); if (container instanceof ItemSetChangeNotifier) { @@ -905,10 +721,9 @@ public class RpcDataProviderExtension extends AbstractExtension { // Send current rows again if needed. if (refreshCache) { - int firstRow = activeRowHandler.activeRange.getStart(); - int numberOfRows = activeRowHandler.activeRange.length(); - - pushRowData(firstRow, numberOfRows, firstRow, numberOfRows); + for (Object itemId : activeItemHandler.getActiveItemIds()) { + internalUpdateRowData(itemId); + } } } @@ -936,7 +751,6 @@ public class RpcDataProviderExtension extends AbstractExtension { List itemIds = container.getItemIds(fullRange.getStart(), fullRange.length()); - keyMapper.setActiveRows(itemIds); JsonArray rows = Json.createArray(); @@ -948,14 +762,16 @@ public class RpcDataProviderExtension extends AbstractExtension { for (int i = 0; i < newRange.length() && i + diff < itemIds.size(); ++i) { Object itemId = itemIds.get(i + diff); + rows.set(i, getRowData(getGrid().getColumns(), itemId)); } rpc.setRowData(firstRowToPush, rows); - activeRowHandler.setActiveRows(fullRange); + activeItemHandler.addActiveItems(itemIds); + keyMapper.dropInactiveItems(); } - private JsonValue getRowData(Collection columns, Object itemId) { + private JsonObject getRowData(Collection columns, Object itemId) { Item item = container.getItem(itemId); JsonObject rowData = Json.createObject(); @@ -1072,8 +888,6 @@ public class RpcDataProviderExtension extends AbstractExtension { rpc.insertRowData(index, count); } }); - - activeRowHandler.insertRows(index, count); } /** @@ -1098,8 +912,6 @@ public class RpcDataProviderExtension extends AbstractExtension { rpc.removeRowData(index, count); } }); - - activeRowHandler.removeRows(index, count); } /** @@ -1120,12 +932,9 @@ public class RpcDataProviderExtension extends AbstractExtension { } private void internalUpdateRowData(Object itemId) { - int index = container.indexOfId(itemId); - if (activeRowHandler.activeRange.contains(index)) { - JsonValue row = getRowData(getGrid().getColumns(), itemId); - JsonArray rowArray = Json.createArray(); - rowArray.set(0, row); - rpc.setRowData(index, rowArray); + if (activeItemHandler.getActiveItemIds().contains(itemId)) { + JsonObject row = getRowData(getGrid().getColumns(), itemId); + rpc.updateRowData(row); } } @@ -1143,9 +952,8 @@ public class RpcDataProviderExtension extends AbstractExtension { public void setParent(ClientConnector parent) { if (parent == null) { // We're being detached, release various listeners - - activeRowHandler - .removeValueChangeListeners(activeRowHandler.activeRange); + activeItemHandler.dropListeners(activeItemHandler + .getActiveItemIds()); if (container instanceof ItemSetChangeNotifier) { ((ItemSetChangeNotifier) container) @@ -1171,7 +979,13 @@ public class RpcDataProviderExtension extends AbstractExtension { * a list of removed columns */ public void columnsRemoved(List removedColumns) { - activeRowHandler.columnsRemoved(removedColumns); + for (GridValueChangeListener l : activeItemHandler + .getValueChangeListeners()) { + l.removeColumns(removedColumns); + } + + // No need to resend unchanged data. Client will remember the old + // columns until next set of rows is sent. } /** @@ -1181,7 +995,13 @@ public class RpcDataProviderExtension extends AbstractExtension { * a list of added columns */ public void columnsAdded(List addedColumns) { - activeRowHandler.columnsAdded(addedColumns); + for (GridValueChangeListener l : activeItemHandler + .getValueChangeListeners()) { + l.addColumns(addedColumns); + } + + // Resend all rows to contain new data. + refreshCache(); } public DataProviderKeyMapper getKeyMapper() { diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java index 4bfdb8b6c5..bddbdd113d 100644 --- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java +++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java @@ -20,6 +20,7 @@ import com.vaadin.shared.annotations.NoLayout; import com.vaadin.shared.communication.ClientRpc; import elemental.json.JsonArray; +import elemental.json.JsonObject; /** * RPC interface used for pushing container data to the client. @@ -91,4 +92,15 @@ public interface DataProviderRpc extends ClientRpc { * the size of the new data set */ public void resetDataAndSize(int size); + + /** + * Informs the client that a row has updated. The client-side DataSource + * will map the given data to correct index if it should be in the cache. + * + * @since + * @param row + * the updated row data + */ + @NoLayout + public void updateRowData(JsonObject row); } diff --git a/shared/src/com/vaadin/shared/data/DataRequestRpc.java b/shared/src/com/vaadin/shared/data/DataRequestRpc.java index 0d9b919a4e..84216f0fab 100644 --- a/shared/src/com/vaadin/shared/data/DataRequestRpc.java +++ b/shared/src/com/vaadin/shared/data/DataRequestRpc.java @@ -16,8 +16,8 @@ package com.vaadin.shared.data; -import com.vaadin.shared.annotations.NoLoadingIndicator; import com.vaadin.shared.annotations.Delayed; +import com.vaadin.shared.annotations.NoLoadingIndicator; import com.vaadin.shared.communication.ServerRpc; /** @@ -55,5 +55,17 @@ public interface DataRequestRpc extends ServerRpc { * pinned status of referenced item */ @Delayed + @NoLoadingIndicator public void setPinned(String key, boolean isPinned); + + /** + * Informs the server that an item is dropped from the client cache. + * + * @since + * @param rowKey + * key mapping to item + */ + @Delayed + @NoLoadingIndicator + public void dropRow(String rowKey); } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java index 1032378a2d..3d7f6da587 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java @@ -56,7 +56,6 @@ public class GridDetailsDetach extends AbstractTestUI { layout.addComponent(new Button("Reattach Grid", new Button.ClickListener() { - @Override public void buttonClick(ClickEvent event) { gridContainer.removeAllComponents(); -- cgit v1.2.3 From d40df1dc68f166bc23609d631420a34d1a0f2adf Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Sat, 4 Jul 2015 16:25:43 +0300 Subject: Column collapse events for Table (#6914) Change-Id: Ifeb081086a4231f75f07f4d26c56ec22e72ce5d1 --- server/src/com/vaadin/ui/Table.java | 89 +++++++++++++++- .../com/vaadin/shared/ui/table/TableConstants.java | 1 + .../table/ColumnCollapsingAndColumnExpansion.java | 64 ++++++------ .../ColumnCollapsingAndColumnExpansionTest.java | 112 +++++++++++++++++++++ .../tests/components/table/CustomTableElement.java | 57 +++++++++++ .../table/ColumnCollapsingAndColumnExpansion.html | 97 ------------------ 6 files changed, 291 insertions(+), 129 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java create mode 100644 uitest/src/com/vaadin/tests/components/table/CustomTableElement.java delete mode 100644 uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html (limited to 'shared') diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java index 42c4beab6c..2cd4084ad9 100644 --- a/server/src/com/vaadin/ui/Table.java +++ b/server/src/com/vaadin/ui/Table.java @@ -69,6 +69,7 @@ import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.ui.declarative.DesignException; +import com.vaadin.util.ReflectTools; /** *

@@ -1310,6 +1311,8 @@ public class Table extends AbstractSelect implements Action.Container, * the desired collapsedness. * @throws IllegalStateException * if column collapsing is not allowed + * @throws IllegalArgumentException + * if the property id does not exist */ public void setColumnCollapsed(Object propertyId, boolean collapsed) throws IllegalStateException { @@ -1319,11 +1322,19 @@ public class Table extends AbstractSelect implements Action.Container, if (collapsed && noncollapsibleColumns.contains(propertyId)) { throw new IllegalStateException("The column is noncollapsible!"); } + if (!getContainerPropertyIds().contains(propertyId)) { + throw new IllegalArgumentException("Property '" + propertyId + + "' was not found in the container"); + } if (collapsed) { - collapsedColumns.add(propertyId); + if (collapsedColumns.add(propertyId)) { + fireColumnCollapseEvent(propertyId); + } } else { - collapsedColumns.remove(propertyId); + if (collapsedColumns.remove(propertyId)) { + fireColumnCollapseEvent(propertyId); + } } // Assures the visual refresh @@ -3182,6 +3193,10 @@ public class Table extends AbstractSelect implements Action.Container, } } + private void fireColumnCollapseEvent(Object propertyId) { + fireEvent(new ColumnCollapseEvent(this, propertyId)); + } + private void fireColumnResizeEvent(Object propertyId, int previousWidth, int currentWidth) { /* @@ -5741,6 +5756,53 @@ public class Table extends AbstractSelect implements Action.Container, public void columnReorder(ColumnReorderEvent event); } + /** + * This event is fired when the collapse state of a column changes + */ + public static class ColumnCollapseEvent extends Component.Event { + + public static final Method METHOD = ReflectTools.findMethod( + ColumnCollapseListener.class, "columnCollapseStateChange", + ColumnCollapseEvent.class); + private Object propertyId; + + /** + * Constructor + * + * @param source + * The source of the event + * @param propertyId + * The id of the coumn + */ + public ColumnCollapseEvent(Component source, Object propertyId) { + super(source); + this.propertyId = propertyId; + } + + /** + * Gets the id of the column whose collapse state changed + * + * @return the property id of the column + */ + public Object getPropertyId() { + return propertyId; + } + } + + /** + * Interface for listening to column collapse events. + */ + public interface ColumnCollapseListener extends Serializable { + + /** + * This method is triggered when the collapse state for a column has + * changed + * + * @param event + */ + public void columnCollapseStateChange(ColumnCollapseEvent event); + } + /** * Adds a column reorder listener to the Table. A column reorder listener is * called when a user reorders columns. @@ -5782,6 +5844,29 @@ public class Table extends AbstractSelect implements Action.Container, removeColumnReorderListener(listener); } + /** + * Adds a column collapse listener to the Table. A column collapse listener + * is called when the collapsed state of a column changes. + * + * @param listener + * The listener to attach + */ + public void addColumnCollapseListener(ColumnCollapseListener listener) { + addListener(TableConstants.COLUMN_COLLAPSE_EVENT_ID, + ColumnCollapseEvent.class, listener, ColumnCollapseEvent.METHOD); + } + + /** + * Removes a column reorder listener from the Table. + * + * @param listener + * The listener to remove + */ + public void removeColumnCollapseListener(ColumnCollapseListener listener) { + removeListener(TableConstants.COLUMN_COLLAPSE_EVENT_ID, + ColumnCollapseEvent.class, listener); + } + /** * Set the item description generator which generates tooltips for cells and * rows in the Table diff --git a/shared/src/com/vaadin/shared/ui/table/TableConstants.java b/shared/src/com/vaadin/shared/ui/table/TableConstants.java index fd1c61c772..e782492e9d 100644 --- a/shared/src/com/vaadin/shared/ui/table/TableConstants.java +++ b/shared/src/com/vaadin/shared/ui/table/TableConstants.java @@ -23,6 +23,7 @@ public class TableConstants implements Serializable { public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick"; public static final String COLUMN_RESIZE_EVENT_ID = "columnResize"; public static final String COLUMN_REORDER_EVENT_ID = "columnReorder"; + public static final String COLUMN_COLLAPSE_EVENT_ID = "columnCollapse"; @Deprecated public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft"; diff --git a/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java index 08b65e2ef5..8d1a9fc7df 100644 --- a/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java +++ b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java @@ -2,21 +2,22 @@ package com.vaadin.tests.components.table; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; -import com.vaadin.tests.components.TestBase; -import com.vaadin.ui.Alignment; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Table; -import com.vaadin.ui.TextField; +import com.vaadin.ui.Table.ColumnCollapseEvent; +import com.vaadin.ui.Table.ColumnCollapseListener; -public class ColumnCollapsingAndColumnExpansion extends TestBase { +public class ColumnCollapsingAndColumnExpansion extends AbstractTestUIWithLog { private Table table; @Override - public void setup() { + protected void setup(VaadinRequest request) { table = new Table(); @@ -50,41 +51,44 @@ public class ColumnCollapsingAndColumnExpansion extends TestBase { "cell " + 2 + "-" + y, "cell " + 3 + "-" + y, }, new Object()); } - - addComponent(table); - - HorizontalLayout hl = new HorizontalLayout(); - final TextField tf = new TextField("Column name (ColX)"); - Button hide = new Button("Collapse", new ClickListener() { + table.addColumnCollapseListener(new ColumnCollapseListener() { @Override - public void buttonClick(ClickEvent event) { - table.setColumnCollapsed(tf.getValue(), true); - } - - }); + public void columnCollapseStateChange(ColumnCollapseEvent event) { + log("Collapse state for " + event.getPropertyId() + + " changed to " + + table.isColumnCollapsed(event.getPropertyId())); - Button show = new Button("Show", new ClickListener() { - - @Override - public void buttonClick(ClickEvent event) { - table.setColumnCollapsed(tf.getValue(), false); } - }); + addComponent(table); - hl.addComponent(tf); - hl.addComponent(hide); - hl.addComponent(show); - hl.setComponentAlignment(tf, Alignment.BOTTOM_LEFT); - hl.setComponentAlignment(hide, Alignment.BOTTOM_LEFT); - hl.setComponentAlignment(show, Alignment.BOTTOM_LEFT); - addComponent(hl); + for (int i = 1; i <= 3; i++) { + HorizontalLayout hl = new HorizontalLayout(); + final String id = "Col" + i; + Button hide = new Button("Collapse " + id, new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + table.setColumnCollapsed(id, true); + } + }); + + Button show = new Button("Show " + id, new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + table.setColumnCollapsed(id, false); + } + }); + + hl.addComponent(hide); + hl.addComponent(show); + addComponent(hl); + } } @Override - protected String getDescription() { + protected String getTestDescription() { return "After hiding column 2 the remaining columns (1 and 3) should use all available space in the table"; } diff --git a/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java new file mode 100644 index 0000000000..739851de2f --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.table; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.parallel.BrowserUtil; +import com.vaadin.tests.components.table.CustomTableElement.ContextMenuElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class ColumnCollapsingAndColumnExpansionTest extends MultiBrowserTest { + + @Test + public void expandCorrectlyAfterCollapse() throws IOException { + openTestURL(); + + CustomTableElement table = $(CustomTableElement.class).first(); + + // Hide col2 through UI + table.openCollapseMenu().getItem(1).click(); + compareScreen(table, "col1-col3"); + + // Hide col1 using button + ButtonElement hide1 = $(ButtonElement.class).caption("Collapse Col1") + .first(); + hide1.click(); + compareScreen(table, "col3"); + + // Show column 2 using context menu (first action) + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + // IE8 can context click but the popup is off screen so it can't be + // interacted with... + ButtonElement show2 = $(ButtonElement.class).caption("Show Col2") + .first(); + show2.click(); + } else { + contextClick(table.getCell(0, 0)); + ContextMenuElement contextMenu = table.getContextMenu(); + WebElement i = contextMenu.getItem(0); + i.click(); + } + compareScreen(table, "col2-col3"); + + // Show column 1 again + ButtonElement show1 = $(ButtonElement.class).caption("Show Col1") + .first(); + show1.click(); + + compareScreen(table, "col1-col2-col3"); + } + + private void contextClick(TestBenchElement e) { + if (e.isPhantomJS()) { + JavascriptExecutor js = e.getCommandExecutor(); + String scr = "var element=arguments[0];" + + "var ev = document.createEvent('HTMLEvents');" + + "ev.initEvent('contextmenu', true, false);" + + "element.dispatchEvent(ev);"; + js.executeScript(scr, e); + } else { + e.contextClick(); + } + + } + + @Test + public void collapseEvents() { + openTestURL(); + CustomTableElement table = $(CustomTableElement.class).first(); + + // Through menu + table.openCollapseMenu().getItem(0).click(); + Assert.assertEquals("1. Collapse state for Col1 changed to true", + getLogRow(0)); + + // Through button + $(ButtonElement.class).caption("Collapse Col2").first().click(); + Assert.assertEquals("2. Collapse state for Col2 changed to true", + getLogRow(0)); + + // Show through menu + table.openCollapseMenu().getItem(1).click(); + Assert.assertEquals("3. Collapse state for Col1 changed to false", + getLogRow(0)); + + // Show through button + $(ButtonElement.class).caption("Show Col2").first().click(); + Assert.assertEquals("4. Collapse state for Col2 changed to false", + getLogRow(0)); + + } +} diff --git a/uitest/src/com/vaadin/tests/components/table/CustomTableElement.java b/uitest/src/com/vaadin/tests/components/table/CustomTableElement.java new file mode 100644 index 0000000000..f1df98fb38 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/CustomTableElement.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.table; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.TableElement; +import com.vaadin.testbench.elementsbase.AbstractElement; +import com.vaadin.testbench.elementsbase.ServerClass; + +@ServerClass("com.vaadin.ui.Table") +public class CustomTableElement extends TableElement { + + public CollapseMenu openCollapseMenu() { + getCollapseMenuToggle().click(); + WebElement cm = getDriver().findElement( + By.xpath("//*[@id='PID_VAADIN_CM']")); + return wrapElement(cm, getCommandExecutor()).wrap(CollapseMenu.class); + } + + public static class CollapseMenu extends ContextMenuElement { + } + + public WebElement getCollapseMenuToggle() { + return findElement(By.className("v-table-column-selector")); + } + + public static class ContextMenuElement extends AbstractElement { + + public WebElement getItem(int index) { + return findElement(By.xpath(".//table//tr[" + (index + 1) + + "]//td/*")); + } + + } + + public ContextMenuElement getContextMenu() { + WebElement cm = getDriver().findElement(By.className("v-contextmenu")); + return wrapElement(cm, getCommandExecutor()).wrap( + ContextMenuElement.class); + } + +} diff --git a/uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html b/uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html deleted file mode 100644 index 4dc63721a1..0000000000 --- a/uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - -ColumnCollapsingAndColumnExpansion - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ColumnCollapsingAndColumnExpansion
open/run/com.vaadin.tests.components.table.ColumnCollapsingAndColumnExpansion?restartApplication
screenCapturecol1-col2-col3-visible
clickvaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[0]/domChild[1]
mouseClick//tr[2]/td/span/div23,2
screenCapturecol2-hidden
enterCharactervaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VTextField[0]Col1
clickvaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]/domChild[0]
screenCapturecol1-col2-hidden
contextmenuvaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[2]/domChild[0]/domChild[0]
mouseClickvaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VContextMenu[0]#option011,6
enterCharactervaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VTextField[0]Col2
screenCapturecol1-hidden
enterCharactervaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VTextField[0]Col1
clickvaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[2]/VButton[0]/domChild[0]/domChild[0]
screenCapturecol1-col2-col3-visible-again
- - -- cgit v1.2.3 From 5db6f100da4c66ce394dffdfe16689be188314f1 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Fri, 21 Aug 2015 14:17:17 +0300 Subject: Fix AbstractRemoteDataSource cache clearing (#18630) This patch also reduces the amount of RPC calls when dropping rows from cache. Change-Id: Ib69a807883bc885dcd877a008cec16e44fa2bfdd --- .../client/connectors/RpcDataSourceConnector.java | 12 ++++-- .../client/data/AbstractRemoteDataSource.java | 45 ++++++++++++++++++---- .../com/vaadin/data/RpcDataProviderExtension.java | 9 +++-- .../src/com/vaadin/shared/data/DataRequestRpc.java | 10 +++-- 4 files changed, 57 insertions(+), 19 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index 5680b09cef..79838f3252 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -107,10 +107,16 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class); private DetailsListener detailsListener; + private JsonArray droppedRowKeys = Json.createArray(); @Override protected void requestRows(int firstRowIndex, int numberOfRows, RequestRowsCallback 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. @@ -235,10 +241,8 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { } @Override - protected void onDropFromCache(int rowIndex) { - super.onDropFromCache(rowIndex); - - rpcProxy.dropRow(getRowKey(getRow(rowIndex))); + protected void onDropFromCache(int rowIndex, JsonObject row) { + droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row)); } } diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index c8910f8699..256bc5ff6a 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -287,8 +287,7 @@ public abstract class AbstractRemoteDataSource implements DataSource { * Simple case: no overlap between cached data and needed data. * Clear the cache and request new data */ - indexToRowMap.clear(); - keyToIndexMap.clear(); + dropFromCache(cached); cached = Range.between(0, 0); handleMissingRows(getMaxCacheRange()); @@ -330,27 +329,48 @@ public abstract class AbstractRemoteDataSource implements DataSource { private void dropFromCache(Range range) { for (int i = range.getStart(); i < range.getEnd(); i++) { - // Called before dropping from cache, so we can actually do - // something with the data before the drop. - onDropFromCache(i); - + // Called after dropping from cache. Dropped row is passed as a + // parameter, but is no longer present in the DataSource T removed = indexToRowMap.remove(Integer.valueOf(i)); + onDropFromCache(i, removed); keyToIndexMap.remove(getRowKey(removed)); } } /** - * A hook that can be overridden to do something whenever a row is about to - * be dropped from the cache. + * A hook that can be overridden to do something whenever a row has been + * dropped from the cache. DataSource no longer has anything in the given + * index. + *

+ * NOTE: This method has been replaced. Override + * {@link #onDropFromCache(int, Object)} instead of this method. * * @since 7.5.0 * @param rowIndex * the index of the dropped row + * @deprecated replaced by {@link #onDropFromCache(int, Object)} */ + @Deprecated protected void onDropFromCache(int rowIndex) { // noop } + /** + * A hook that can be overridden to do something whenever a row has been + * dropped from the cache. DataSource no longer has anything in the given + * index. + * + * @since + * @param rowIndex + * the index of the dropped row + * @param removed + * the removed row object + */ + protected void onDropFromCache(int rowIndex, T removed) { + // Call old version as a fallback (someone might have used it) + onDropFromCache(rowIndex); + } + private void handleMissingRows(Range range) { if (range.isEmpty()) { return; @@ -481,6 +501,15 @@ public abstract class AbstractRemoteDataSource implements DataSource { * updated before the widget settings. Support for this will be * implemented later on. */ + + // Run a dummy drop from cache for unused rows. + for (int i = 0; i < partition[0].length(); ++i) { + onDropFromCache(i + partition[0].getStart(), rowData.get(i)); + } + + for (int i = 0; i < partition[2].length(); ++i) { + onDropFromCache(i + partition[2].getStart(), rowData.get(i)); + } } // Eventually check whether all needed rows are now available diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 403cc4c016..2ec100eba6 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -123,7 +123,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * the item ids for which to get keys * @return keys for the {@code itemIds} */ - public List getKeys(Collection itemIds) { + public List getKeys(Collection itemIds) { if (itemIds == null) { throw new IllegalArgumentException("itemIds can't be null"); } @@ -698,8 +698,11 @@ public class RpcDataProviderExtension extends AbstractExtension { } @Override - public void dropRow(String rowKey) { - activeItemHandler.dropActiveItem(keyMapper.getItemId(rowKey)); + public void dropRows(JsonArray rowKeys) { + for (int i = 0; i < rowKeys.length(); ++i) { + activeItemHandler.dropActiveItem(keyMapper + .getItemId(rowKeys.getString(i))); + } } }); diff --git a/shared/src/com/vaadin/shared/data/DataRequestRpc.java b/shared/src/com/vaadin/shared/data/DataRequestRpc.java index 84216f0fab..041e92d05c 100644 --- a/shared/src/com/vaadin/shared/data/DataRequestRpc.java +++ b/shared/src/com/vaadin/shared/data/DataRequestRpc.java @@ -20,6 +20,8 @@ import com.vaadin.shared.annotations.Delayed; import com.vaadin.shared.annotations.NoLoadingIndicator; import com.vaadin.shared.communication.ServerRpc; +import elemental.json.JsonArray; + /** * RPC interface used for requesting container data to the client. * @@ -59,13 +61,13 @@ public interface DataRequestRpc extends ServerRpc { public void setPinned(String key, boolean isPinned); /** - * Informs the server that an item is dropped from the client cache. + * Informs the server that items have been dropped from the client cache. * * @since - * @param rowKey - * key mapping to item + * @param rowKeys + * array of dropped keys mapping to items */ @Delayed @NoLoadingIndicator - public void dropRow(String rowKey); + public void dropRows(JsonArray rowKeys); } -- cgit v1.2.3 From b6f2bb0ceeb2e903e3c457f480ebd66450597644 Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Wed, 26 Aug 2015 16:47:19 +0300 Subject: Fix empty @since tags for 7.6.0.alpha4 Change-Id: I9f223ec2d49a4a851f5e5808cc325c52717191ee --- client/src/com/vaadin/client/ComputedStyle.java | 4 ++-- .../com/vaadin/client/connectors/RpcDataSourceConnector.java | 2 +- .../src/com/vaadin/client/data/AbstractRemoteDataSource.java | 2 +- client/src/com/vaadin/client/widgets/Grid.java | 2 +- server/src/com/vaadin/data/DataGenerator.java | 2 +- server/src/com/vaadin/data/RpcDataProviderExtension.java | 12 ++++++------ .../server/communication/AtmospherePushConnection.java | 2 +- server/src/com/vaadin/ui/Grid.java | 12 ++++++------ shared/src/com/vaadin/shared/data/DataProviderRpc.java | 2 +- shared/src/com/vaadin/shared/data/DataRequestRpc.java | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) (limited to 'shared') diff --git a/client/src/com/vaadin/client/ComputedStyle.java b/client/src/com/vaadin/client/ComputedStyle.java index 676d18d245..66404eeeed 100644 --- a/client/src/com/vaadin/client/ComputedStyle.java +++ b/client/src/com/vaadin/client/ComputedStyle.java @@ -335,7 +335,7 @@ public class ComputedStyle { /** * Returns the sum of the top and bottom margin * - * @since + * @since 7.6 * @return the sum of the top and bottom margin */ public double getMarginHeight() { @@ -348,7 +348,7 @@ public class ComputedStyle { /** * Returns the sum of the top and bottom margin * - * @since + * @since 7.6 * @return the sum of the left and right margin */ public double getMarginWidth() { diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index 79838f3252..78aaebaca1 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -229,7 +229,7 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { /** * Updates row data based on row key. * - * @since + * @since 7.6 * @param row * new row object */ diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 256bc5ff6a..2438bec8df 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -360,7 +360,7 @@ public abstract class AbstractRemoteDataSource implements DataSource { * dropped from the cache. DataSource no longer has anything in the given * index. * - * @since + * @since 7.6 * @param rowIndex * the index of the dropped row * @param removed diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 580d0da72b..45d14fac30 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -8302,7 +8302,7 @@ public class Grid extends ResizeComposite implements * Returns a CellReference for the cell to which the given element belongs * to. * - * @since + * @since 7.6 * @param element * Element to find from the cell's content. * @return CellReference or null if cell was not found. diff --git a/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java index f025623a3e..a5333b8523 100644 --- a/server/src/com/vaadin/data/DataGenerator.java +++ b/server/src/com/vaadin/data/DataGenerator.java @@ -29,7 +29,7 @@ import elemental.json.JsonObject; * {@link AbstractRenderer} implements this interface to provide encoded data to * client for {@link Renderer}s automatically. * - * @since + * @since 7.6 * @author Vaadin Ltd */ public interface DataGenerator extends Serializable { diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 2ec100eba6..f5d712f6b2 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -243,7 +243,7 @@ public class RpcDataProviderExtension extends AbstractExtension { /** * {@inheritDoc} * - * @since + * @since 7.6 */ @Override public void generateData(Object itemId, Item item, JsonObject rowData) { @@ -253,7 +253,7 @@ public class RpcDataProviderExtension extends AbstractExtension { /** * Removes all inactive item id to key mapping from the key mapper. * - * @since + * @since 7.6 */ public void dropInactiveItems() { Collection active = activeItemHandler.getActiveItemIds(); @@ -270,7 +270,7 @@ public class RpcDataProviderExtension extends AbstractExtension { /** * Class for keeping track of current items and ValueChangeListeners. * - * @since + * @since 7.6 */ private class ActiveItemHandler implements Serializable { @@ -545,7 +545,7 @@ public class RpcDataProviderExtension extends AbstractExtension { /** * {@inheritDoc} * - * @since + * @since 7.6 */ @Override public void generateData(Object itemId, Item item, JsonObject rowData) { @@ -823,7 +823,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * DataGenerators are called when sending row data to client. If given * DataGenerator is already added, this method does nothing. * - * @since + * @since 7.6 * @param generator * generator to add */ @@ -836,7 +836,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * {@code RpcDataProviderExtension}. If given DataGenerator is not added to * this data provider, this method does nothing. * - * @since + * @since 7.6 * @param generator * generator to remove */ diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java index e1bc8e212b..05e4f6479f 100644 --- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java +++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java @@ -359,7 +359,7 @@ public class AtmospherePushConnection implements PushConnection { * Internal method used for reconfiguring loggers to show all Atmosphere log * messages in the console. * - * @since + * @since 7.6 */ public static void enableAtmosphereDebugLogging() { Level level = Level.FINEST; diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index c7ad9632fa..cea89d24e8 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -1574,7 +1574,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator) * - * @since + * @since 7.6 */ public interface RowDescriptionGenerator extends Serializable { @@ -1598,7 +1598,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator) * - * @since + * @since 7.6 */ public interface CellDescriptionGenerator extends Serializable { @@ -5877,7 +5877,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @see #setRowDescriptionGenerator(RowDescriptionGenerator) * - * @since + * @since 7.6 */ public void setCellDescriptionGenerator(CellDescriptionGenerator generator) { cellDescriptionGenerator = generator; @@ -5891,7 +5891,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @return the description generator or {@code null} if no generator is set * - * @since + * @since 7.6 */ public CellDescriptionGenerator getCellDescriptionGenerator() { return cellDescriptionGenerator; @@ -5911,7 +5911,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @see #setCellDescriptionGenerator(CellDescriptionGenerator) * - * @since + * @since 7.6 */ public void setRowDescriptionGenerator(RowDescriptionGenerator generator) { rowDescriptionGenerator = generator; @@ -5925,7 +5925,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @return the description generator or {@code} null if no generator is set * - * @since + * @since 7.6 */ public RowDescriptionGenerator getRowDescriptionGenerator() { return rowDescriptionGenerator; diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java index bddbdd113d..28e50d9747 100644 --- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java +++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java @@ -97,7 +97,7 @@ public interface DataProviderRpc extends ClientRpc { * Informs the client that a row has updated. The client-side DataSource * will map the given data to correct index if it should be in the cache. * - * @since + * @since 7.6 * @param row * the updated row data */ diff --git a/shared/src/com/vaadin/shared/data/DataRequestRpc.java b/shared/src/com/vaadin/shared/data/DataRequestRpc.java index 041e92d05c..b66965fae9 100644 --- a/shared/src/com/vaadin/shared/data/DataRequestRpc.java +++ b/shared/src/com/vaadin/shared/data/DataRequestRpc.java @@ -63,7 +63,7 @@ public interface DataRequestRpc extends ServerRpc { /** * Informs the server that items have been dropped from the client cache. * - * @since + * @since 7.6 * @param rowKeys * array of dropped keys mapping to items */ -- cgit v1.2.3 From 53a4b2c77a6af86c157884c62e6193911242a7f9 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Mon, 6 Jul 2015 10:01:17 +0300 Subject: Refactor Grid SelectionModels as extensions (#18624) This patch removes all selection related variables and API from several core parts of Grid. Change-Id: Idb7aa48fda69ded1ef58a69c1f7dbc78b7f52a54 --- WebContent/release-notes.html | 2 + .../AbstractSelectionModelConnector.java | 82 +++++ .../vaadin/client/connectors/GridConnector.java | 207 ------------ .../connectors/MultiSelectionModelConnector.java | 360 +++++++++++++++++++++ .../connectors/NoSelectionModelConnector.java | 42 +++ .../client/connectors/RpcDataSourceConnector.java | 6 +- .../connectors/SingleSelectionModelConnector.java | 148 +++++++++ client/src/com/vaadin/client/widgets/Grid.java | 12 +- .../com/vaadin/data/RpcDataProviderExtension.java | 25 +- server/src/com/vaadin/ui/Grid.java | 317 +++++++++--------- .../com/vaadin/shared/data/DataProviderRpc.java | 12 +- .../com/vaadin/shared/ui/grid/GridClientRpc.java | 11 - .../com/vaadin/shared/ui/grid/GridServerRpc.java | 4 - .../src/com/vaadin/shared/ui/grid/GridState.java | 15 +- .../selection/MultiSelectionModelServerRpc.java | 55 ++++ .../grid/selection/MultiSelectionModelState.java | 31 ++ .../selection/SingleSelectionModelServerRpc.java | 35 ++ .../grid/selection/SingleSelectionModelState.java | 30 ++ .../components/grid/GridCustomSelectionModel.java | 37 +++ .../grid/GridCustomSelectionModelTest.java | 58 ++++ .../client/grid/MySelectionModelConnector.java | 61 ++++ 21 files changed, 1135 insertions(+), 415 deletions(-) create mode 100644 client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java create mode 100644 client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java create mode 100644 client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java create mode 100644 client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java create mode 100644 shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java create mode 100644 shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java create mode 100644 shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java create mode 100644 shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java create mode 100644 uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java create mode 100644 uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java create mode 100644 uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java (limited to 'shared') diff --git a/WebContent/release-notes.html b/WebContent/release-notes.html index 61511b3002..e9b4a24ce1 100644 --- a/WebContent/release-notes.html +++ b/WebContent/release-notes.html @@ -119,6 +119,8 @@ This may interfere with custom response compression solutions that do not respect the Content-Encoding response header.
  • Unused methods related to the "out of sync" message have been removed from SystemMessages class.
  • All notifications use the WAI-ARIA alert role to be compatible with Jaws
  • +
  • Grid SelectionModels are now Extensions. This update removes all selection related variables and API from + GridConnector, GridState, GridServerRpc and GridClientRpc
  • Known Issues and Limitations

      diff --git a/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java new file mode 100644 index 0000000000..8ca2292bc5 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import java.util.Collection; + +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.extensions.AbstractExtensionConnector; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.grid.GridState; + +import elemental.json.JsonObject; + +/** + * Base class for all selection model connectors. + * + * @since + * @author Vaadin Ltd + */ +public abstract class AbstractSelectionModelConnector> + extends AbstractExtensionConnector { + + @Override + public GridConnector getParent() { + return (GridConnector) super.getParent(); + } + + protected Grid getGrid() { + return getParent().getWidget(); + } + + protected RowHandle 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 { + + @Override + public boolean isSelected(JsonObject row) { + return row.hasKey(GridState.JSONKEY_SELECTED); + } + + @Override + public void setGrid(Grid grid) { + // NO-OP + } + + @Override + public void reset() { + // Should not need any actions. + } + + @Override + public Collection getSelectedRows() { + throw new UnsupportedOperationException( + "This client-side selection model " + + getClass().getSimpleName() + + " does not know selected rows."); + } + } +} diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 5f9341c068..1070a46287 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -36,7 +35,6 @@ 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.CheckBox; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; @@ -48,8 +46,6 @@ import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.Renderer; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.AbstractHasComponentsConnector; import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; @@ -72,15 +68,6 @@ import com.vaadin.client.widget.grid.events.EditorMoveEvent; import com.vaadin.client.widget.grid.events.EditorOpenEvent; import com.vaadin.client.widget.grid.events.GridClickEvent; import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; -import com.vaadin.client.widget.grid.events.SelectAllEvent; -import com.vaadin.client.widget.grid.events.SelectAllHandler; -import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionEvent; -import com.vaadin.client.widget.grid.selection.SelectionHandler; -import com.vaadin.client.widget.grid.selection.SelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionModelMulti; -import com.vaadin.client.widget.grid.selection.SelectionModelNone; -import com.vaadin.client.widget.grid.selection.SelectionModelSingle; import com.vaadin.client.widget.grid.sort.SortEvent; import com.vaadin.client.widget.grid.sort.SortHandler; import com.vaadin.client.widget.grid.sort.SortOrder; @@ -99,7 +86,6 @@ import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode; import com.vaadin.shared.ui.grid.GridStaticSectionState; import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState; import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState; @@ -584,19 +570,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements */ private Map columnIdToColumn = new HashMap(); - private AbstractRowHandleSelectionModel selectionModel; - private Set selectedKeys = new LinkedHashSet(); private List columnOrder = new ArrayList(); - /** - * {@link #selectionUpdatedFromState} is set to true when - * {@link #updateSelectionFromState()} makes changes to selection. This flag - * tells the {@code internalSelectionChangeHandler} to not send same data - * straight back to server. Said listener sets it back to false when - * handling that event. - */ - private boolean selectionUpdatedFromState; - /** * {@link #columnsUpdatedFromState} is set to true when * {@link #updateColumnOrderFromState(List)} is updating the column order @@ -608,29 +583,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements private RpcDataSource dataSource; - private SelectionHandler internalSelectionChangeHandler = new SelectionHandler() { - @Override - public void onSelect(SelectionEvent event) { - if (event.isBatchedSelection()) { - return; - } - if (!selectionUpdatedFromState) { - for (JsonObject row : event.getRemoved()) { - selectedKeys.remove(dataSource.getRowKey(row)); - } - - for (JsonObject row : event.getAdded()) { - selectedKeys.add(dataSource.getRowKey(row)); - } - - getRpcProxy(GridServerRpc.class).select( - new ArrayList(selectedKeys)); - } else { - selectionUpdatedFromState = false; - } - } - }; - /* Used to track Grid editor columns with validation errors */ private final Map, String> columnToErrorMessage = new HashMap, String>(); @@ -744,30 +696,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void recalculateColumnWidths() { getWidget().recalculateColumnWidths(); } - - @Override - public void setSelectAll(boolean allSelected) { - if (selectionModel instanceof SelectionModel.Multi - && selectionModel.getSelectionColumnRenderer() != null) { - final Widget widget; - try { - HeaderRow defaultHeaderRow = getWidget() - .getDefaultHeaderRow(); - if (defaultHeaderRow != null) { - widget = defaultHeaderRow.getCell( - getWidget().getColumn(0)).getWidget(); - ((CheckBox) widget).setValue(allSelected, false); - } - } catch (Exception e) { - getLogger().warning( - "Problems finding select all checkbox."); - } - } - } }); - getWidget().addSelectionHandler(internalSelectionChangeHandler); - /* Item click events */ getWidget().addBodyClickHandler(itemClickHandler); getWidget().addBodyDoubleClickHandler(itemClickHandler); @@ -800,15 +730,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }); - getWidget().addSelectAllHandler(new SelectAllHandler() { - - @Override - public void onSelectAll(SelectAllEvent event) { - getRpcProxy(GridServerRpc.class).selectAll(); - } - - }); - getWidget().setEditorHandler(editorHandler); getWidget().addColumnReorderHandler(columnReorderHandler); getWidget().addColumnVisibilityChangeHandler( @@ -884,19 +805,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements updateFooterFromState(getState().footer); } - // Selection - if (stateChangeEvent.hasPropertyChanged("selectionMode")) { - onSelectionModeChange(); - updateSelectDeselectAllowed(); - } else if (stateChangeEvent - .hasPropertyChanged("singleSelectDeselectAllowed")) { - updateSelectDeselectAllowed(); - } - - if (stateChangeEvent.hasPropertyChanged("selectedKeys")) { - updateSelectionFromState(); - } - // Sorting if (stateChangeEvent.hasPropertyChanged("sortColumns") || stateChangeEvent.hasPropertyChanged("sortDirs")) { @@ -923,14 +831,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - private void updateSelectDeselectAllowed() { - SelectionModel model = getWidget().getSelectionModel(); - if (model instanceof SelectionModel.Single) { - ((SelectionModel.Single) model) - .setDeselectAllowed(getState().singleSelectDeselectAllowed); - } - } - private void updateColumnOrderFromState(List stateColumnOrder) { CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder .size()]; @@ -1102,20 +1002,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements columnOrder.add(state.id); } - /** - * If we have a selection column renderer, we need to offset the index by - * one when referring to the column index in the widget. - */ - private int getWidgetColumnIndex(final int columnIndex) { - Renderer selectionColumnRenderer = getWidget() - .getSelectionModel().getSelectionColumnRenderer(); - int widgetColumnIndex = columnIndex; - if (selectionColumnRenderer != null) { - widgetColumnIndex++; - } - return widgetColumnIndex; - } - /** * Updates the column values from a state * @@ -1178,63 +1064,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().setDataSource(this.dataSource); } - private void onSelectionModeChange() { - SharedSelectionMode mode = getState().selectionMode; - if (mode == null) { - getLogger().fine("ignored mode change"); - return; - } - - AbstractRowHandleSelectionModel model = createSelectionModel(mode); - if (selectionModel == null - || !model.getClass().equals(selectionModel.getClass())) { - selectionModel = model; - getWidget().setSelectionModel(model); - selectedKeys.clear(); - } - } - - private void updateSelectionFromState() { - boolean changed = false; - - List stateKeys = getState().selectedKeys; - - // find new deselections - for (String key : selectedKeys) { - if (!stateKeys.contains(key)) { - changed = true; - deselectByHandle(dataSource.getHandleByKey(key)); - } - } - - // find new selections - for (String key : stateKeys) { - if (!selectedKeys.contains(key)) { - changed = true; - selectByHandle(dataSource.getHandleByKey(key)); - } - } - - /* - * A defensive copy in case the collection in the state is mutated - * instead of re-assigned. - */ - selectedKeys = new LinkedHashSet(stateKeys); - - /* - * We need to fire this event so that Grid is able to re-render the - * selection changes (if applicable). - */ - if (changed) { - // At least for now there's no way to send the selected and/or - // deselected row data. Some data is only stored as keys - selectionUpdatedFromState = true; - getWidget().fireEvent( - new SelectionEvent(getWidget(), - (List) null, null, false)); - } - } - private void onSortStateChange() { List sortOrder = new ArrayList(); @@ -1253,41 +1082,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements return Logger.getLogger(getClass().getName()); } - @SuppressWarnings("static-method") - private AbstractRowHandleSelectionModel createSelectionModel( - SharedSelectionMode mode) { - switch (mode) { - case SINGLE: - return new SelectionModelSingle(); - case MULTI: - return new SelectionModelMulti(); - case NONE: - return new SelectionModelNone(); - default: - throw new IllegalStateException("unexpected mode value: " + mode); - } - } - - /** - * A workaround method for accessing the protected method - * {@code AbstractRowHandleSelectionModel.selectByHandle} - */ - private native void selectByHandle(RowHandle handle) - /*-{ - var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel; - model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle); - }-*/; - - /** - * A workaround method for accessing the protected method - * {@code AbstractRowHandleSelectionModel.deselectByHandle} - */ - private native void deselectByHandle(RowHandle handle) - /*-{ - var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel; - model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle); - }-*/; - /** * Gets the row key for a row object. * @@ -1312,7 +1106,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void updateCaption(ComponentConnector connector) { // TODO Auto-generated method stub - } @Override diff --git a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java new file mode 100644 index 0000000000..c1f5d87d68 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java @@ -0,0 +1,360 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.DataAvailableEvent; +import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.events.SelectAllEvent; +import com.vaadin.client.widget.grid.events.SelectAllHandler; +import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.HeaderCell; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.ui.Grid.MultiSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link MultiSelectionModel}. + * + * @since + * @author Vaadin Ltd + */ +@Connect(MultiSelectionModel.class) +public class MultiSelectionModelConnector extends + AbstractSelectionModelConnector> { + + private Multi selectionModel = createSelectionModel(); + private SpaceSelectHandler spaceHandler; + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(selectionModel); + spaceHandler = new SpaceSelectHandler(getGrid()); + } + + @Override + public void onUnregister() { + spaceHandler.removeHandler(); + } + + @Override + protected Multi 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 { + + private ComplexRenderer renderer = null; + private Set> selected = new HashSet>(); + private Set> deselected = new HashSet>(); + private HandlerRegistration selectAll; + private HandlerRegistration dataAvailable; + private Range availableRows; + private boolean batchSelect = false; + + @Override + public void setGrid(Grid grid) { + super.setGrid(grid); + if (grid != null) { + renderer = createSelectionColumnRenderer(grid); + selectAll = getGrid().addSelectAllHandler( + new SelectAllHandler() { + + @Override + public void onSelectAll( + SelectAllEvent event) { + selectAll(); + } + }); + dataAvailable = getGrid().addDataAvailableHandler( + new DataAvailableHandler() { + + @Override + public void onDataAvailable(DataAvailableEvent event) { + availableRows = event.getAvailableRows(); + } + }); + } else if (renderer != null) { + renderer.destroy(); + selectAll.removeHandler(); + dataAvailable.removeHandler(); + renderer = null; + } + } + + /** + * Creates a selection column renderer. This method can be overridden to + * use a custom renderer or use {@code null} to disable the selection + * column. + * + * @param grid + * the grid for this selection model + * @return selection column renderer or {@code null} if not needed + */ + protected ComplexRenderer createSelectionColumnRenderer( + Grid grid) { + return new MultiSelectionRenderer(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."; + + Set> rows = new HashSet>(); + DataSource dataSource = getGrid().getDataSource(); + for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { + final JsonObject row = dataSource.getRow(i); + if (row != null) { + RowHandle handle = dataSource.getHandle(row); + markAsSelected(handle, true); + rows.add(handle); + } + } + + getRpcProxy(MultiSelectionModelServerRpc.class).selectAll(); + cleanRowCache(rows); + } + + @Override + public Renderer 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."; + + Set> rows = new HashSet>(); + DataSource dataSource = getGrid().getDataSource(); + for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { + final JsonObject row = dataSource.getRow(i); + if (row != null) { + RowHandle handle = dataSource.getHandle(row); + markAsSelected(handle, false); + rows.add(handle); + } + } + + getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll(); + cleanRowCache(rows); + + return true; + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean select(Collection rows) { + if (rows.isEmpty()) { + return false; + } + + for (JsonObject row : rows) { + RowHandle rowHandle = getRowHandle(row); + markAsSelected(rowHandle, true); + selected.add(rowHandle); + } + + if (!isBeingBatchSelected()) { + sendSelected(); + } + return true; + } + + /** + * Marks the JsonObject pointed by RowHandle to have selected + * information equal to given boolean + * + * @param row + * row handle + * @param selected + * should row be selected + */ + protected void markAsSelected(RowHandle row, + boolean selected) { + row.pin(); + if (selected) { + row.getRow().put(GridState.JSONKEY_SELECTED, true); + } else { + row.getRow().remove(GridState.JSONKEY_SELECTED); + } + row.updateRow(); + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean deselect(Collection rows) { + if (rows.isEmpty()) { + return false; + } + + for (JsonObject row : rows) { + RowHandle rowHandle = getRowHandle(row); + markAsSelected(rowHandle, false); + deselected.add(rowHandle); + } + + if (!isBeingBatchSelected()) { + sendDeselected(); + } + return true; + } + + private void sendDeselected() { + getRpcProxy(MultiSelectionModelServerRpc.class).deselect( + getRowKeys(deselected)); + cleanRowCache(deselected); + } + + private void sendSelected() { + getRpcProxy(MultiSelectionModelServerRpc.class).select( + getRowKeys(selected)); + cleanRowCache(selected); + } + + private List getRowKeys(Set> handles) { + List keys = new ArrayList(); + for (RowHandle handle : handles) { + keys.add(getRowKey(handle.getRow())); + } + return keys; + } + + private Set getRows(Set> handles) { + Set rows = new HashSet(); + for (RowHandle handle : handles) { + rows.add(handle.getRow()); + } + return rows; + } + + private void cleanRowCache(Set> handles) { + for (RowHandle handle : handles) { + handle.unpin(); + } + handles.clear(); + } + + @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 getSelectedRowsBatch() { + return Collections.unmodifiableSet(getRows(selected)); + } + + @Override + public Collection getDeselectedRowsBatch() { + return Collections.unmodifiableSet(getRows(deselected)); + } + } +} diff --git a/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java new file mode 100644 index 0000000000..b540eed6d5 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModelNone; +import com.vaadin.shared.ui.Connect; +import com.vaadin.ui.Grid.NoSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link NoSelectionModel}. + */ +@Connect(NoSelectionModel.class) +public class NoSelectionModelConnector extends + AbstractSelectionModelConnector> { + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(createSelectionModel()); + } + + @Override + protected SelectionModel createSelectionModel() { + return new SelectionModelNone(); + } +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index 78aaebaca1..bcca8816b1 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -99,8 +99,10 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { } @Override - public void updateRowData(JsonObject row) { - RpcDataSource.this.updateRowData(row); + public void updateRowData(JsonArray rowArray) { + for (int i = 0; i < rowArray.length(); ++i) { + RpcDataSource.this.updateRowData(rowArray.getObject(i)); + } } }); } diff --git a/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java new file mode 100644 index 0000000000..7c66903c2c --- /dev/null +++ b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java @@ -0,0 +1,148 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Single; +import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; +import com.vaadin.ui.Grid.SingleSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link SingleSelectionModel}. + * + * @since + * @author Vaadin Ltd + */ +@Connect(SingleSelectionModel.class) +public class SingleSelectionModelConnector extends + AbstractSelectionModelConnector> { + + private SpaceSelectHandler spaceHandler; + private ClickSelectHandler clickHandler; + private Single selectionModel = createSelectionModel(); + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(selectionModel); + spaceHandler = new SpaceSelectHandler(getGrid()); + clickHandler = new ClickSelectHandler(getGrid()); + } + + @Override + public SingleSelectionModelState getState() { + return (SingleSelectionModelState) super.getState(); + } + + @Override + public void onUnregister() { + spaceHandler.removeHandler(); + clickHandler.removeHandler(); + + super.onUnregister(); + } + + @Override + protected Single 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 { + + private RowHandle selectedRow; + private boolean deselectAllowed; + + @Override + public Renderer getSelectionColumnRenderer() { + return null; + } + + @Override + public boolean select(JsonObject row) { + boolean changed = false; + if ((row == null && isDeselectAllowed()) + || (row != null && !getRowHandle(row).equals(selectedRow))) { + if (selectedRow != null) { + selectedRow.getRow().remove(GridState.JSONKEY_SELECTED); + selectedRow.updateRow(); + selectedRow.unpin(); + selectedRow = null; + changed = true; + } + + if (row != null) { + selectedRow = getRowHandle(row); + selectedRow.pin(); + selectedRow.getRow().put(GridState.JSONKEY_SELECTED, true); + selectedRow.updateRow(); + changed = true; + } + } + + if (changed) { + getRpcProxy(SingleSelectionModelServerRpc.class).select( + getRowKey(row)); + } + + return changed; + } + + @Override + public boolean deselect(JsonObject row) { + if (getRowHandle(row).equals(selectedRow)) { + select(null); + } + return false; + } + + @Override + public JsonObject getSelectedRow() { + throw new UnsupportedOperationException( + "This client-side selection model " + + getClass().getSimpleName() + + " does not know selected row."); + } + + @Override + public void setDeselectAllowed(boolean deselectAllowed) { + this.deselectAllowed = deselectAllowed; + } + + @Override + public boolean isDeselectAllowed() { + return deselectAllowed; + } + } +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index a2eedf4203..91fc87d2c7 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -5287,7 +5287,7 @@ public class Grid extends ResizeComposite implements @Override public void preDetach(Row row, Iterable cellsToDetach) { for (FlyweightCell cell : cellsToDetach) { - Renderer renderer = findRenderer(cell); + Renderer renderer = findRenderer(cell); if (renderer instanceof WidgetRenderer) { try { Widget w = WidgetUtil.findWidget(cell.getElement() @@ -5317,7 +5317,7 @@ public class Grid extends ResizeComposite implements // any more rowReference.set(rowIndex, null, row.getElement()); for (FlyweightCell cell : detachedCells) { - Renderer renderer = findRenderer(cell); + Renderer renderer = findRenderer(cell); if (renderer instanceof ComplexRenderer) { try { Column column = getVisibleColumn(cell.getColumn()); @@ -7233,12 +7233,12 @@ public class Grid extends ResizeComposite implements * if the current selection model is not an instance of * {@link SelectionModel.Single} or {@link SelectionModel.Multi} */ - @SuppressWarnings("unchecked") public boolean select(T row) { if (selectionModel instanceof SelectionModel.Single) { return ((SelectionModel.Single) selectionModel).select(row); } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel).select(row); + return ((SelectionModel.Multi) selectionModel) + .select(Collections.singleton(row)); } else { throw new IllegalStateException("Unsupported selection model"); } @@ -7258,12 +7258,12 @@ public class Grid extends ResizeComposite implements * if the current selection model is not an instance of * {@link SelectionModel.Single} or {@link SelectionModel.Multi} */ - @SuppressWarnings("unchecked") public boolean deselect(T row) { if (selectionModel instanceof SelectionModel.Single) { return ((SelectionModel.Single) selectionModel).deselect(row); } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel).deselect(row); + return ((SelectionModel.Multi) selectionModel) + .deselect(Collections.singleton(row)); } else { throw new IllegalStateException("Unsupported selection model"); } diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index f5d712f6b2..c8f3604fd9 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -744,15 +744,11 @@ public class RpcDataProviderExtension extends AbstractExtension { // Send current rows again if needed. if (refreshCache) { - for (Object itemId : activeItemHandler.getActiveItemIds()) { - internalUpdateRowData(itemId); - } + updatedItemIds.addAll(activeItemHandler.getActiveItemIds()); } } - for (Object itemId : updatedItemIds) { - internalUpdateRowData(itemId); - } + internalUpdateRows(updatedItemIds); // Clear all changes. rowChanges.clear(); @@ -913,11 +909,20 @@ public class RpcDataProviderExtension extends AbstractExtension { updatedItemIds.add(itemId); } - private void internalUpdateRowData(Object itemId) { - if (activeItemHandler.getActiveItemIds().contains(itemId)) { - JsonObject row = getRowData(getGrid().getColumns(), itemId); - rpc.updateRowData(row); + private void internalUpdateRows(Set itemIds) { + if (itemIds.isEmpty()) { + return; + } + + JsonArray rowData = Json.createArray(); + int i = 0; + for (Object itemId : itemIds) { + if (activeItemHandler.getActiveItemIds().contains(itemId)) { + JsonObject row = getRowData(getGrid().getColumns(), itemId); + rowData.set(i++, row); + } } + rpc.updateRowData(rowData); } /** diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index d9c011677b..f58280c6fe 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -82,6 +82,7 @@ import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.AbstractExtension; import com.vaadin.server.EncodeResult; import com.vaadin.server.ErrorMessage; +import com.vaadin.server.Extension; import com.vaadin.server.JsonCodec; import com.vaadin.server.KeyMapper; import com.vaadin.server.VaadinSession; @@ -94,13 +95,16 @@ import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode; import com.vaadin.shared.ui.grid.GridStaticCellType; 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.HeightMode; import com.vaadin.shared.ui.grid.ScrollDestination; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; @@ -111,7 +115,6 @@ import com.vaadin.ui.renderers.TextRenderer; import com.vaadin.util.ReflectTools; import elemental.json.Json; -import elemental.json.JsonArray; import elemental.json.JsonObject; import elemental.json.JsonValue; @@ -710,8 +713,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, /** * The server-side interface that controls Grid's selection state. + * SelectionModel should extend {@link AbstractGridExtension}. */ - public interface SelectionModel extends Serializable { + public interface SelectionModel extends Serializable, Extension { /** * Checks whether an item is selected or not. * @@ -730,6 +734,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, /** * Injects the current {@link Grid} instance into the SelectionModel. + * This method should usually call the extend method of + * {@link AbstractExtension}. *

      * Note: This method should not be called manually. * @@ -971,10 +977,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * A base class for SelectionModels that contains some of the logic that is * reusable. */ - public static abstract class AbstractSelectionModel implements - SelectionModel { + public static abstract class AbstractSelectionModel extends + AbstractGridExtension implements SelectionModel, DataGenerator { protected final LinkedHashSet selection = new LinkedHashSet(); - protected Grid grid = null; @Override public boolean isSelected(final Object itemId) { @@ -988,7 +993,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void setGrid(final Grid grid) { - this.grid = grid; + if (grid != null) { + extend(grid); + } } /** @@ -1002,7 +1009,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ protected void checkItemIdExists(Object itemId) throws IllegalArgumentException { - if (!grid.getContainerDataSource().containsId(itemId)) { + if (!getParentGrid().getContainerDataSource().containsId(itemId)) { throw new IllegalArgumentException("Given item id (" + itemId + ") does not exist in the container"); } @@ -1044,7 +1051,19 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, protected void fireSelectionEvent( final Collection oldSelection, final Collection newSelection) { - grid.fireSelectionEvent(oldSelection, newSelection); + getParentGrid().fireSelectionEvent(oldSelection, newSelection); + } + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + if (isSelected(itemId)) { + rowData.put(GridState.JSONKEY_SELECTED, true); + } + } + + @Override + protected Object getItemId(String rowKey) { + return rowKey != null ? super.getItemId(rowKey) : null; } } @@ -1053,8 +1072,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ public static class SingleSelectionModel extends AbstractSelectionModel implements SelectionModel.Single { + + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + registerRpc(new SingleSelectionModelServerRpc() { + + @Override + public void select(String rowKey) { + SingleSelectionModel.this.select(getItemId(rowKey), false); + } + }); + } + @Override public boolean select(final Object itemId) { + return select(itemId, true); + } + + protected boolean select(final Object itemId, boolean refresh) { if (itemId == null) { return deselect(getSelectedRow()); } @@ -1066,7 +1102,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, if (modified) { final Collection deselected; if (selectedRow != null) { - deselectInternal(selectedRow, false); + deselectInternal(selectedRow, false, true); deselected = Collections.singleton(selectedRow); } else { deselected = Collections.emptySet(); @@ -1075,19 +1111,28 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, fireSelectionEvent(deselected, selection); } + if (refresh) { + refreshRow(itemId); + } + return modified; } private boolean deselect(final Object itemId) { - return deselectInternal(itemId, true); + return deselectInternal(itemId, true, true); } private boolean deselectInternal(final Object itemId, - boolean fireEventIfNeeded) { + boolean fireEventIfNeeded, boolean refresh) { final boolean modified = selection.remove(itemId); - if (fireEventIfNeeded && modified) { - fireSelectionEvent(Collections.singleton(itemId), - Collections.emptySet()); + if (modified) { + if (refresh) { + refreshRow(itemId); + } + if (fireEventIfNeeded) { + fireSelectionEvent(Collections.singleton(itemId), + Collections.emptySet()); + } } return modified; } @@ -1113,23 +1158,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void setDeselectAllowed(boolean deselectAllowed) { - grid.getState().singleSelectDeselectAllowed = deselectAllowed; + getState().deselectAllowed = deselectAllowed; } @Override public boolean isDeselectAllowed() { - return grid.getState(false).singleSelectDeselectAllowed; + return getState().deselectAllowed; + } + + @Override + protected SingleSelectionModelState getState() { + return (SingleSelectionModelState) super.getState(); } } /** * A default implementation for a {@link SelectionModel.None} */ - public static class NoSelectionModel implements SelectionModel.None { - @Override - public void setGrid(final Grid grid) { - // NOOP, not needed for anything - } + public static class NoSelectionModel extends AbstractSelectionModel + implements SelectionModel.None { @Override public boolean isSelected(final Object itemId) { @@ -1167,7 +1214,40 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, private int selectionLimit = DEFAULT_MAX_SELECTIONS; - private boolean allSelected; + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + registerRpc(new MultiSelectionModelServerRpc() { + + @Override + public void select(List rowKeys) { + List items = new ArrayList(); + for (String rowKey : rowKeys) { + items.add(getItemId(rowKey)); + } + MultiSelectionModel.this.select(items, false); + } + + @Override + public void deselect(List rowKeys) { + List items = new ArrayList(); + for (String rowKey : rowKeys) { + items.add(getItemId(rowKey)); + } + MultiSelectionModel.this.deselect(items, false); + } + + @Override + public void selectAll() { + MultiSelectionModel.this.selectAll(false); + } + + @Override + public void deselectAll() { + MultiSelectionModel.this.deselectAll(false); + } + }); + } @Override public boolean select(final Object... itemIds) @@ -1190,6 +1270,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public boolean select(final Collection itemIds) throws IllegalArgumentException { + return select(itemIds, true); + } + + protected boolean select(final Collection itemIds, boolean refresh) { if (itemIds == null) { throw new IllegalArgumentException("itemIds may not be null"); } @@ -1217,6 +1301,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, updateAllSelectedState(); + if (refresh) { + for (Object itemId : itemIds) { + refreshRow(itemId); + } + } + return selectionWillChange; } @@ -1270,6 +1360,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public boolean deselect(final Collection itemIds) throws IllegalArgumentException { + return deselect(itemIds, true); + } + + protected boolean deselect(final Collection itemIds, boolean refresh) { if (itemIds == null) { throw new IllegalArgumentException("itemIds may not be null"); } @@ -1285,15 +1379,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, updateAllSelectedState(); + if (refresh) { + for (Object itemId : itemIds) { + refreshRow(itemId); + } + } + return hasCommonElements; } @Override public boolean selectAll() { + return selectAll(true); + } + + protected boolean selectAll(boolean refresh) { // select will fire the event - final Indexed container = grid.getContainerDataSource(); + final Indexed container = getParentGrid().getContainerDataSource(); if (container != null) { - return select(container.getItemIds()); + return select(container.getItemIds(), refresh); } else if (selection.isEmpty()) { return false; } else { @@ -1302,14 +1406,18 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * but I guess the only theoretically correct course of * action... */ - return deselectAll(); + return deselectAll(false); } } @Override public boolean deselectAll() { + return deselectAll(true); + } + + protected boolean deselectAll(boolean refresh) { // deselect will fire the event - return deselect(getSelectedRows()); + return deselect(getSelectedRows(), refresh); } /** @@ -1382,11 +1490,15 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } private void updateAllSelectedState() { - if (allSelected != selection.size() >= selectionLimit) { - allSelected = selection.size() >= selectionLimit; - grid.getRpcProxy(GridClientRpc.class).setSelectAll(allSelected); + if (getState().allSelected != selection.size() >= selectionLimit) { + getState().allSelected = selection.size() >= selectionLimit; } } + + @Override + protected MultiSelectionModelState getState() { + return (MultiSelectionModelState) super.getState(); + } } /** @@ -1571,7 +1683,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * Grid rows. If a description is generated for a row, it is used for all * the cells in the row for which a {@link CellDescriptionGenerator cell * description} is not generated. - * + * * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator) * * @since 7.6 @@ -3783,6 +3895,17 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, + " instead"); } } + + /** + * Resends the row data for given item id to the client. + * + * @since + * @param itemId + * row to refresh + */ + protected void refreshRow(Object itemId) { + getParentGrid().datasourceExtension.updateRowData(itemId); + } } /** @@ -3982,116 +4105,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ private void initGrid() { setSelectionMode(getDefaultSelectionMode()); - addSelectionListener(new SelectionListener() { - @Override - public void select(SelectionEvent event) { - if (applyingSelectionFromClient) { - /* - * Avoid sending changes back to the client if they - * originated from the client. Instead, the RPC handler is - * responsible for keeping track of the resulting selection - * state and notifying the client if it doens't match the - * expectation. - */ - return; - } - - /* - * The rows are pinned here to ensure that the client gets the - * correct key from server when the selected row is first - * loaded. - * - * Once the client has gotten info that it is supposed to select - * a row, it will pin the data from the client side as well and - * it will be unpinned once it gets deselected. Nothing on the - * server side should ever unpin anything from KeyMapper. - * Pinning is mostly a client feature and is only used when - * selecting something from the server side. - */ - for (Object addedItemId : event.getAdded()) { - if (!getKeyMapper().isPinned(addedItemId)) { - getKeyMapper().pin(addedItemId); - } - } - - getState().selectedKeys = getKeyMapper().getKeys( - getSelectedRows()); - } - }); registerRpc(new GridServerRpc() { - @Override - public void select(List selection) { - Collection receivedSelection = getKeyMapper() - .getItemIds(selection); - - applyingSelectionFromClient = true; - try { - SelectionModel selectionModel = getSelectionModel(); - if (selectionModel instanceof SelectionModel.Single - && selection.size() <= 1) { - Object select = null; - if (selection.size() == 1) { - select = getKeyMapper().getItemId(selection.get(0)); - } - ((SelectionModel.Single) selectionModel).select(select); - } else if (selectionModel instanceof SelectionModel.Multi) { - ((SelectionModel.Multi) selectionModel) - .setSelected(receivedSelection); - } else { - throw new IllegalStateException("SelectionModel " - + selectionModel.getClass().getSimpleName() - + " does not support selecting the given " - + selection.size() + " items."); - } - } finally { - applyingSelectionFromClient = false; - } - - Collection actualSelection = getSelectedRows(); - - // Make sure all selected rows are pinned - for (Object itemId : actualSelection) { - if (!getKeyMapper().isPinned(itemId)) { - getKeyMapper().pin(itemId); - } - } - - // Don't mark as dirty since this might be the expected state - getState(false).selectedKeys = getKeyMapper().getKeys( - actualSelection); - - JsonObject diffState = getUI().getConnectorTracker() - .getDiffState(Grid.this); - - final String diffstateKey = "selectedKeys"; - - assert diffState.hasKey(diffstateKey) : "Field name has changed"; - - if (receivedSelection.equals(actualSelection)) { - /* - * We ended up with the same selection state that the client - * sent us. There's nothing to send back to the client, just - * update the diffstate so subsequent changes will be - * detected. - */ - JsonArray diffSelected = Json.createArray(); - for (String rowKey : getState(false).selectedKeys) { - diffSelected.set(diffSelected.length(), rowKey); - } - diffState.put(diffstateKey, diffSelected); - } else { - /* - * Actual selection is not what the client expects. Make - * sure the client gets a state change event by clearing the - * diffstate and marking as dirty - */ - diffState.remove(diffstateKey); - markAsDirty(); - } - } - @Override public void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated) { @@ -4120,13 +4136,6 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } } - @Override - public void selectAll() { - assert getSelectionModel() instanceof SelectionModel.Multi : "Not a multi selection model!"; - - ((SelectionModel.Multi) getSelectionModel()).selectAll(); - } - @Override public void itemClick(String rowKey, String columnId, MouseEventDetails details) { @@ -5019,25 +5028,11 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, if (this.selectionModel != selectionModel) { // this.selectionModel is null on init if (this.selectionModel != null) { - this.selectionModel.reset(); - this.selectionModel.setGrid(null); + this.selectionModel.remove(); } this.selectionModel = selectionModel; - this.selectionModel.setGrid(this); - this.selectionModel.reset(); - - if (selectionModel.getClass().equals(SingleSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.SINGLE; - } else if (selectionModel.getClass().equals( - MultiSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.MULTI; - } else if (selectionModel.getClass().equals(NoSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.NONE; - } else { - throw new UnsupportedOperationException("Grid currently " - + "supports only its own bundled selection models"); - } + selectionModel.setGrid(this); } } diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java index 28e50d9747..05965ea56c 100644 --- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java +++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java @@ -20,7 +20,6 @@ import com.vaadin.shared.annotations.NoLayout; import com.vaadin.shared.communication.ClientRpc; import elemental.json.JsonArray; -import elemental.json.JsonObject; /** * RPC interface used for pushing container data to the client. @@ -94,13 +93,14 @@ public interface DataProviderRpc extends ClientRpc { public void resetDataAndSize(int size); /** - * Informs the client that a row has updated. The client-side DataSource - * will map the given data to correct index if it should be in the cache. + * Informs the client that rows have been updated. The client-side + * DataSource will map the given data to correct index if it should be in + * the cache. * * @since 7.6 - * @param row - * the updated row data + * @param rowArray + * array of updated rows */ @NoLayout - public void updateRowData(JsonObject row); + public void updateRowData(JsonArray rowArray); } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 8711a757a1..ac1b1d5a78 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -55,15 +55,4 @@ public interface GridClientRpc extends ClientRpc { * Command client Grid to recalculate column widths. */ public void recalculateColumnWidths(); - - /** - * Informs the Grid that all items have been selected or not selected on the - * server side. - * - * @since 7.5.3 - * @param allSelected - * true to check the select all checkbox, - * false to uncheck it. - */ - public void setSelectAll(boolean allSelected); } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index a178ff63d0..8d64794b41 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -29,10 +29,6 @@ import com.vaadin.shared.data.sort.SortDirection; */ public interface GridServerRpc extends ServerRpc { - void select(List newSelection); - - void selectAll(); - void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated); diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index 0d0a5d3e9f..e51b1a78a7 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -128,6 +128,13 @@ public class GridState extends TabIndexState { * */ public static final String JSONKEY_DETAILS_VISIBLE = "dv"; + /** + * The key that tells whether row is selected. + * + * @since + */ + public static final String JSONKEY_SELECTED = "s"; + /** * Columns in grid. */ @@ -153,14 +160,6 @@ public class GridState extends TabIndexState { @DelegateToWidget public HeightMode heightMode = HeightMode.CSS; - // instantiated just to avoid NPEs - public List selectedKeys = new ArrayList(); - - public SharedSelectionMode selectionMode; - - /** Whether single select mode can be cleared through the UI */ - public boolean singleSelectDeselectAllowed = true; - /** Keys of the currently sorted columns */ public String[] sortColumns = new String[0]; diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java new file mode 100644 index 0000000000..e7324552f4 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import java.util.List; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * ServerRpc for MultiSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public interface MultiSelectionModelServerRpc extends ServerRpc { + + /** + * Select a list of rows based on their row keys on the server-side. + * + * @param rowKeys + * list of row keys to select + */ + public void select(List rowKeys); + + /** + * Deselect a list of rows based on their row keys on the server-side. + * + * @param rowKeys + * list of row keys to deselect + */ + public void deselect(List rowKeys); + + /** + * Selects all rows on the server-side. + */ + public void selectAll(); + + /** + * Deselects all rows on the server-side. + */ + public void deselectAll(); +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java new file mode 100644 index 0000000000..3ffd0d0f93 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import com.vaadin.shared.communication.SharedState; + +/** + * SharedState object for MultiSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public class MultiSelectionModelState extends SharedState { + + /* Select All -checkbox status */ + public boolean allSelected; + +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java new file mode 100644 index 0000000000..3e2a8ec847 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * ServerRpc for SingleSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public interface SingleSelectionModelServerRpc extends ServerRpc { + + /** + * Selects a row on server-side. + * + * @param rowKey + * row key of selected row; {@code null} if deselect + */ + public void select(String rowKey); +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java new file mode 100644 index 0000000000..719b60b2ba --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import com.vaadin.shared.communication.SharedState; + +/** + * SharedState object for SingleSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public class SingleSelectionModelState extends SharedState { + + /* Allow deselecting rows */ + public boolean deselectAllowed; +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java new file mode 100644 index 0000000000..008c24cdd3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.widgetset.TestingWidgetSet; +import com.vaadin.ui.Grid.MultiSelectionModel; + +@Widgetset(TestingWidgetSet.NAME) +public class GridCustomSelectionModel extends AbstractTestUI { + + public static class MySelectionModel extends MultiSelectionModel { + } + + @Override + protected void setup(VaadinRequest request) { + PersonTestGrid grid = new PersonTestGrid(500); + grid.setSelectionModel(new MySelectionModel()); + addComponent(grid); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java new file mode 100644 index 0000000000..976e1e78fe --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.elements.GridElement.GridCellElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridCustomSelectionModelTest extends MultiBrowserTest { + + @Test + public void testCustomSelectionModel() { + setDebug(true); + openTestURL(); + + GridElement grid = $(GridElement.class).first(); + GridCellElement cell = grid.getCell(0, 0); + assertTrue("First column of Grid should not have an input element", + cell.findElements(By.className("input")).isEmpty()); + + assertFalse("Row should not be selected initially", grid.getRow(0) + .isSelected()); + + cell.click(5, 5); + assertTrue("Click should select row", grid.getRow(0).isSelected()); + cell.click(5, 5); + assertFalse("Click should deselect row", grid.getRow(0).isSelected()); + + grid.sendKeys(Keys.SPACE); + assertTrue("Space should select row", grid.getRow(0).isSelected()); + grid.sendKeys(Keys.SPACE); + assertFalse("Space should deselect row", grid.getRow(0).isSelected()); + + assertNoErrorNotifications(); + } +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java new file mode 100644 index 0000000000..81a9ab5bf1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.widgetset.client.grid; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.connectors.MultiSelectionModelConnector; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.Connect; +import com.vaadin.tests.components.grid.GridCustomSelectionModel.MySelectionModel; + +import elemental.json.JsonObject; + +@Connect(MySelectionModel.class) +public class MySelectionModelConnector extends MultiSelectionModelConnector { + + private ClickSelectHandler handler; + + @Override + protected void extend(ServerConnector target) { + super.extend(target); + handler = new ClickSelectHandler(getGrid()); + } + + @Override + public void onUnregister() { + super.onUnregister(); + handler.removeHandler(); + handler = null; + } + + @Override + protected Multi createSelectionModel() { + return new MySelectionModel(); + } + + public class MySelectionModel extends MultiSelectionModel { + + @Override + protected ComplexRenderer createSelectionColumnRenderer( + Grid grid) { + // No Selection Column. + return null; + } + } +} -- cgit v1.2.3 From ac66a3d17446571c6ebace16cb3930df44381ad7 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Mon, 31 Aug 2015 14:37:30 +0300 Subject: Redesign RpcDataSourceConnector pinning RPC requests (#18692) This patch removes DataProviderKeyMapper which was mostly dead code already. Uses a regular KeyMapper instead. Change-Id: Ic97d1dc827d45fde65bcddc0414bfe711032620c --- .../connectors/MultiSelectionModelConnector.java | 75 ++++--- .../client/connectors/RpcDataSourceConnector.java | 20 +- .../client/data/AbstractRemoteDataSource.java | 10 +- .../com/vaadin/data/RpcDataProviderExtension.java | 249 ++------------------- server/src/com/vaadin/ui/Grid.java | 19 +- .../component/grid/DataProviderExtension.java | 88 -------- .../src/com/vaadin/shared/data/DataRequestRpc.java | 14 -- .../tests/components/grid/CustomRendererTest.java | 4 +- .../components/grid/JavaScriptRenderersTest.java | 2 +- 9 files changed, 91 insertions(+), 390 deletions(-) delete mode 100644 server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java (limited to 'shared') diff --git a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java index c1f5d87d68..e4ad50e7ac 100644 --- a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java +++ b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java @@ -156,19 +156,16 @@ public class MultiSelectionModelConnector extends public void selectAll() { assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection."; - Set> rows = new HashSet>(); DataSource dataSource = getGrid().getDataSource(); for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { final JsonObject row = dataSource.getRow(i); if (row != null) { RowHandle handle = dataSource.getHandle(row); markAsSelected(handle, true); - rows.add(handle); } } getRpcProxy(MultiSelectionModelServerRpc.class).selectAll(); - cleanRowCache(rows); } @Override @@ -205,19 +202,16 @@ public class MultiSelectionModelConnector extends public boolean deselectAll() { assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection."; - Set> rows = new HashSet>(); DataSource dataSource = getGrid().getDataSource(); for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { final JsonObject row = dataSource.getRow(i); if (row != null) { RowHandle handle = dataSource.getHandle(row); markAsSelected(handle, false); - rows.add(handle); } } getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll(); - cleanRowCache(rows); return true; } @@ -235,8 +229,9 @@ public class MultiSelectionModelConnector extends for (JsonObject row : rows) { RowHandle rowHandle = getRowHandle(row); - markAsSelected(rowHandle, true); - selected.add(rowHandle); + if (markAsSelected(rowHandle, true)) { + selected.add(rowHandle); + } } if (!isBeingBatchSelected()) { @@ -246,23 +241,35 @@ public class MultiSelectionModelConnector extends } /** - * Marks the JsonObject pointed by RowHandle to have selected - * information equal to given boolean + * Marks the given row to be selected or deselected. Returns true if the + * value actually changed. + *

      + * Note: If selection model is in batch select state, the row will be + * pinned on select. * * @param row * row handle * @param selected - * should row be selected + * {@code true} if row should be selected; {@code false} if + * not + * @return {@code true} if selected status changed; {@code false} if not */ - protected void markAsSelected(RowHandle row, + protected boolean markAsSelected(RowHandle row, boolean selected) { - row.pin(); - if (selected) { + if (selected && !isSelected(row.getRow())) { row.getRow().put(GridState.JSONKEY_SELECTED, true); - } else { + } else if (!selected && isSelected(row.getRow())) { row.getRow().remove(GridState.JSONKEY_SELECTED); + } else { + return false; } + row.updateRow(); + + if (isBeingBatchSelected()) { + row.pin(); + } + return true; } /** @@ -278,8 +285,9 @@ public class MultiSelectionModelConnector extends for (JsonObject row : rows) { RowHandle rowHandle = getRowHandle(row); - markAsSelected(rowHandle, false); - deselected.add(rowHandle); + if (markAsSelected(rowHandle, false)) { + deselected.add(rowHandle); + } } if (!isBeingBatchSelected()) { @@ -288,16 +296,38 @@ public class MultiSelectionModelConnector extends 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)); - cleanRowCache(deselected); + + if (isBeingBatchSelected()) { + for (RowHandle 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)); - cleanRowCache(selected); + + if (isBeingBatchSelected()) { + for (RowHandle row : selected) { + row.unpin(); + } + } + + selected.clear(); } private List getRowKeys(Set> handles) { @@ -316,13 +346,6 @@ public class MultiSelectionModelConnector extends return rows; } - private void cleanRowCache(Set> handles) { - for (RowHandle handle : handles) { - handle.unpin(); - } - handles.clear(); - } - @Override public void startBatchSelect() { assert selected.isEmpty() && deselected.isEmpty() : "Row caches were not clear."; diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index bcca8816b1..5daa02c3bf 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -189,24 +189,16 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { return new RowHandleImpl(row, key); } - @Override - protected void pinHandle(RowHandleImpl handle) { - // Server only knows if something is pinned or not. No need to pin - // multiple times. - boolean pinnedBefore = handle.isPinned(); - super.pinHandle(handle); - if (!pinnedBefore) { - rpcProxy.setPinned(getRowKey(handle.getRow()), true); - } - } - @Override protected void unpinHandle(RowHandleImpl handle) { // Row data is no longer available after it has been unpinned. String key = getRowKey(handle.getRow()); super.unpinHandle(handle); if (!handle.isPinned()) { - rpcProxy.setPinned(key, false); + if (indexOfKey(key) == -1) { + // Row out of view has been unpinned. drop it + droppedRowKeys.set(droppedRowKeys.length(), key); + } } } @@ -244,7 +236,9 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { @Override protected void onDropFromCache(int rowIndex, JsonObject row) { - droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row)); + if (!((RowHandleImpl) getHandle(row)).isPinned()) { + droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row)); + } } } diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 2438bec8df..58cd5c5f19 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -16,8 +16,6 @@ package com.vaadin.client.data; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -120,12 +118,7 @@ public abstract class AbstractRemoteDataSource implements DataSource { @Override public T getRow() throws IllegalStateException { - if (isPinned()) { - return row; - } else { - throw new IllegalStateException("The row handle for key " + key - + " was not pinned"); - } + return row; } public boolean isPinned() { @@ -197,7 +190,6 @@ public abstract class AbstractRemoteDataSource implements DataSource { private Map pinnedCounts = new HashMap(); private Map pinnedRows = new HashMap(); - protected Collection temporarilyPinnedRows = Collections.emptySet(); // Size not yet known private int size = -1; diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index c8f3604fd9..78c87ab23d 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -21,14 +21,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import com.google.gwt.thirdparty.guava.common.collect.BiMap; -import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Sets; @@ -43,6 +40,7 @@ import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Property.ValueChangeNotifier; import com.vaadin.server.AbstractExtension; import com.vaadin.server.ClientConnector; +import com.vaadin.server.KeyMapper; import com.vaadin.shared.data.DataProviderRpc; import com.vaadin.shared.data.DataRequestRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -70,215 +68,22 @@ import elemental.json.JsonObject; */ public class RpcDataProviderExtension extends AbstractExtension { - /** - * ItemId to Key to ItemId mapper. - *

      - * This class is used when transmitting information about items in container - * related to Grid. It introduces a consistent way of mapping ItemIds and - * its container to a String that can be mapped back to ItemId. - *

      - * Technical note: This class also keeps tabs on which indices are - * being shown/selected, and is able to clean up after itself once the - * itemId ⇆ key mapping is not needed anymore. In other words, this - * doesn't leak memory. - */ - public class DataProviderKeyMapper implements Serializable, DataGenerator { - private final BiMap itemIdToKey = HashBiMap.create(); - private Set pinnedItemIds = new HashSet(); - private long rollingIndex = 0; - - private DataProviderKeyMapper() { - // private implementation - } - - private String nextKey() { - return String.valueOf(rollingIndex++); - } - - /** - * Gets the key for a given item id. Creates a new key mapping if no - * existing mapping was found for the given item id. - * - * @since 7.5.0 - * @param itemId - * the item id to get the key for - * @return the key for the given item id - */ - public String getKey(Object itemId) { - String key = itemIdToKey.get(itemId); - if (key == null) { - key = nextKey(); - itemIdToKey.put(itemId, key); - } - return key; - } - - /** - * Gets keys for a collection of item ids. - *

      - * If the itemIds are currently cached, the existing keys will be used. - * Otherwise new ones will be created. - * - * @param itemIds - * the item ids for which to get keys - * @return keys for the {@code itemIds} - */ - public List getKeys(Collection itemIds) { - if (itemIds == null) { - throw new IllegalArgumentException("itemIds can't be null"); - } - - ArrayList keys = new ArrayList(itemIds.size()); - for (Object itemId : itemIds) { - keys.add(getKey(itemId)); - } - return keys; - } - - /** - * Gets the registered item id based on its key. - *

      - * A key is used to identify a particular row on both a server and a - * client. This method can be used to get the item id for the row key - * that the client has sent. - * - * @param key - * the row key for which to retrieve an item id - * @return the item id corresponding to {@code key} - * @throws IllegalStateException - * if the key mapper does not have a record of {@code key} . - */ - public Object getItemId(String key) throws IllegalStateException { - Object itemId = itemIdToKey.inverse().get(key); - if (itemId != null) { - return itemId; - } else { - throw new IllegalStateException("No item id for key " + key - + " found."); - } - } - - /** - * Gets corresponding item ids for each of the keys in a collection. - * - * @param keys - * the keys for which to retrieve item ids - * @return a collection of item ids for the {@code keys} - * @throws IllegalStateException - * if one or more of keys don't have a corresponding item id - * in the cache - */ - public Collection getItemIds(Collection keys) - throws IllegalStateException { - if (keys == null) { - throw new IllegalArgumentException("keys may not be null"); - } - - ArrayList itemIds = new ArrayList(keys.size()); - for (String key : keys) { - itemIds.add(getItemId(key)); - } - return itemIds; - } - - /** - * Pin an item id to be cached indefinitely. - *

      - * Normally when an itemId is not an active row, it is discarded from - * the cache. Pinning an item id will make sure that it is kept in the - * cache. - *

      - * In effect, while an item id is pinned, it always has the same key. - * - * @param itemId - * the item id to pin - * @throws IllegalStateException - * if {@code itemId} was already pinned - * @see #unpin(Object) - * @see #isPinned(Object) - * @see #getItemIds(Collection) - */ - public void pin(Object itemId) throws IllegalStateException { - if (isPinned(itemId)) { - throw new IllegalStateException("Item id " + itemId - + " was pinned already"); - } - pinnedItemIds.add(itemId); - } - - /** - * Unpin an item id. - *

      - * This cancels the effect of pinning an item id. If the item id is - * currently inactive, it will be immediately removed from the cache. - * - * @param itemId - * the item id to unpin - * @throws IllegalStateException - * if {@code itemId} was not pinned - * @see #pin(Object) - * @see #isPinned(Object) - * @see #getItemIds(Collection) - */ - public void unpin(Object itemId) throws IllegalStateException { - if (!isPinned(itemId)) { - throw new IllegalStateException("Item id " + itemId - + " was not pinned"); - } - - pinnedItemIds.remove(itemId); - } - - /** - * Checks whether an item id is pinned or not. - * - * @param itemId - * the item id to check for pin status - * @return {@code true} iff the item id is currently pinned - */ - public boolean isPinned(Object itemId) { - return pinnedItemIds.contains(itemId); - } - - /** - * {@inheritDoc} - * - * @since 7.6 - */ - @Override - public void generateData(Object itemId, Item item, JsonObject rowData) { - rowData.put(GridState.JSONKEY_ROWKEY, getKey(itemId)); - } - - /** - * Removes all inactive item id to key mapping from the key mapper. - * - * @since 7.6 - */ - public void dropInactiveItems() { - Collection active = activeItemHandler.getActiveItemIds(); - Iterator itemIter = itemIdToKey.keySet().iterator(); - while (itemIter.hasNext()) { - Object itemId = itemIter.next(); - if (!active.contains(itemId) && !isPinned(itemId)) { - itemIter.remove(); - } - } - } - } - /** * Class for keeping track of current items and ValueChangeListeners. * * @since 7.6 */ - private class ActiveItemHandler implements Serializable { + private class ActiveItemHandler implements Serializable, DataGenerator { private final Map activeItemMap = new HashMap(); + private final KeyMapper keyMapper = new KeyMapper(); private final Set droppedItems = new HashSet(); /** - * Registers ValueChangeListeners for given items ids. + * Registers ValueChangeListeners for given item ids. + *

      + * Note: This method will clean up any unneeded listeners and key + * mappings * * @param itemIds * collection of new active item ids @@ -293,7 +98,7 @@ public class RpcDataProviderExtension extends AbstractExtension { // Remove still active rows that were "dropped" droppedItems.removeAll(itemIds); - dropListeners(droppedItems); + internalDropActiveItems(droppedItems); droppedItems.clear(); } @@ -310,11 +115,12 @@ public class RpcDataProviderExtension extends AbstractExtension { } } - private void dropListeners(Collection itemIds) { + private void internalDropActiveItems(Collection itemIds) { for (Object itemId : droppedItems) { assert activeItemMap.containsKey(itemId) : "Item ID should exist in the activeItemMap"; activeItemMap.remove(itemId).removeListener(); + keyMapper.remove(itemId); } } @@ -335,6 +141,12 @@ public class RpcDataProviderExtension extends AbstractExtension { public Collection getValueChangeListeners() { return new HashSet(activeItemMap.values()); } + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + rowData.put(GridState.JSONKEY_ROWKEY, keyMapper.key(itemId)); + } + } /** @@ -635,8 +447,6 @@ public class RpcDataProviderExtension extends AbstractExtension { } }; - private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper(); - /** RpcDataProvider should send the current cache again. */ private boolean refreshCache = false; @@ -683,25 +493,11 @@ public class RpcDataProviderExtension extends AbstractExtension { cacheSize); } - @Override - public void setPinned(String key, boolean isPinned) { - Object itemId = keyMapper.getItemId(key); - if (isPinned) { - // Row might already be pinned if it was selected from the - // server - if (!keyMapper.isPinned(itemId)) { - keyMapper.pin(itemId); - } - } else { - keyMapper.unpin(itemId); - } - } - @Override public void dropRows(JsonArray rowKeys) { for (int i = 0; i < rowKeys.length(); ++i) { - activeItemHandler.dropActiveItem(keyMapper - .getItemId(rowKeys.getString(i))); + activeItemHandler.dropActiveItem(getKeyMapper().get( + rowKeys.getString(i))); } } }); @@ -711,7 +507,7 @@ public class RpcDataProviderExtension extends AbstractExtension { .addItemSetChangeListener(itemListener); } - addDataGenerator(keyMapper); + addDataGenerator(activeItemHandler); addDataGenerator(detailComponentManager); } @@ -787,7 +583,6 @@ public class RpcDataProviderExtension extends AbstractExtension { rpc.setRowData(firstRowToPush, rows); activeItemHandler.addActiveItems(itemIds); - keyMapper.dropInactiveItems(); } private JsonObject getRowData(Collection columns, Object itemId) { @@ -939,7 +734,7 @@ public class RpcDataProviderExtension extends AbstractExtension { public void setParent(ClientConnector parent) { if (parent == null) { // We're being detached, release various listeners - activeItemHandler.dropListeners(activeItemHandler + activeItemHandler.internalDropActiveItems(activeItemHandler .getActiveItemIds()); if (container instanceof ItemSetChangeNotifier) { @@ -987,8 +782,8 @@ public class RpcDataProviderExtension extends AbstractExtension { refreshCache(); } - public DataProviderKeyMapper getKeyMapper() { - return keyMapper; + public KeyMapper getKeyMapper() { + return activeItemHandler.keyMapper; } protected Grid getGrid() { diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 204f771ee5..215df3a6a3 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -57,7 +57,6 @@ import com.vaadin.data.DataGenerator; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.RpcDataProviderExtension; -import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; import com.vaadin.data.RpcDataProviderExtension.DetailComponentManager; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory; @@ -3856,7 +3855,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * @return the item id corresponding to {@code key} */ protected Object getItemId(String rowKey) { - return getParentGrid().getKeyMapper().getItemId(rowKey); + return getParentGrid().getKeyMapper().get(rowKey); } /** @@ -4139,7 +4138,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void itemClick(String rowKey, String columnId, MouseEventDetails details) { - Object itemId = getKeyMapper().getItemId(rowKey); + Object itemId = getKeyMapper().get(rowKey); Item item = datasource.getItem(itemId); Object propertyId = getPropertyIdByColumnId(columnId); fireEvent(new ItemClickEvent(Grid.this, item, itemId, @@ -4222,20 +4221,20 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void editorOpen(String rowKey) { - fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper() - .getItemId(rowKey))); + fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper().get( + rowKey))); } @Override public void editorMove(String rowKey) { - fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper() - .getItemId(rowKey))); + fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper().get( + rowKey))); } @Override public void editorClose(String rowKey) { - fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper() - .getItemId(rowKey))); + fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper().get( + rowKey))); } }); @@ -5299,7 +5298,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * * @return the key mapper being used by the data source */ - DataProviderKeyMapper getKeyMapper() { + KeyMapper getKeyMapper() { return datasourceExtension.getKeyMapper(); } diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java b/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java deleted file mode 100644 index 9ecf131c5b..0000000000 --- a/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.tests.server.component.grid; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; - -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.data.Container; -import com.vaadin.data.Container.Indexed; -import com.vaadin.data.Item; -import com.vaadin.data.Property; -import com.vaadin.data.RpcDataProviderExtension; -import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; -import com.vaadin.data.util.IndexedContainer; - -public class DataProviderExtension { - private RpcDataProviderExtension dataProvider; - private DataProviderKeyMapper keyMapper; - private Container.Indexed container; - - private static final Object ITEM_ID1 = "itemid1"; - private static final Object ITEM_ID2 = "itemid2"; - private static final Object ITEM_ID3 = "itemid3"; - - private static final Object PROPERTY_ID1_STRING = "property1"; - - @Before - public void setup() { - container = new IndexedContainer(); - populate(container); - - dataProvider = new RpcDataProviderExtension(container); - keyMapper = dataProvider.getKeyMapper(); - } - - private static void populate(Indexed container) { - container.addContainerProperty(PROPERTY_ID1_STRING, String.class, ""); - for (Object itemId : Arrays.asList(ITEM_ID1, ITEM_ID2, ITEM_ID3)) { - final Item item = container.addItem(itemId); - @SuppressWarnings("unchecked") - final Property stringProperty = item - .getItemProperty(PROPERTY_ID1_STRING); - stringProperty.setValue(itemId.toString()); - } - } - - @Test - public void pinBasics() { - assertFalse("itemId1 should not start as pinned", - keyMapper.isPinned(ITEM_ID2)); - - keyMapper.pin(ITEM_ID1); - assertTrue("itemId1 should now be pinned", keyMapper.isPinned(ITEM_ID1)); - - keyMapper.unpin(ITEM_ID1); - assertFalse("itemId1 should not be pinned anymore", - keyMapper.isPinned(ITEM_ID2)); - } - - @Test(expected = IllegalStateException.class) - public void doublePinning() { - keyMapper.pin(ITEM_ID1); - keyMapper.pin(ITEM_ID1); - } - - @Test(expected = IllegalStateException.class) - public void nonexistentUnpin() { - keyMapper.unpin(ITEM_ID1); - } -} diff --git a/shared/src/com/vaadin/shared/data/DataRequestRpc.java b/shared/src/com/vaadin/shared/data/DataRequestRpc.java index b66965fae9..4b553dda68 100644 --- a/shared/src/com/vaadin/shared/data/DataRequestRpc.java +++ b/shared/src/com/vaadin/shared/data/DataRequestRpc.java @@ -46,20 +46,6 @@ public interface DataRequestRpc extends ServerRpc { public void requestRows(int firstRowIndex, int numberOfRows, int firstCachedRowIndex, int cacheSize); - /** - * Informs the server that an item referenced with a key pinned status has - * changed. This is a delayed call that happens along with next rpc call to - * server. - * - * @param key - * key mapping to item - * @param isPinned - * pinned status of referenced item - */ - @Delayed - @NoLoadingIndicator - public void setPinned(String key, boolean isPinned); - /** * Informs the server that items have been dropped from the client cache. * diff --git a/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java b/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java index 7d706ecd30..d5e01a9de8 100644 --- a/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java @@ -47,8 +47,8 @@ public class CustomRendererTest extends MultiBrowserTest { .getText()); grid.getCell(0, 1).click(); - assertEquals("row: 0, key: 0", grid.getCell(0, 1).getText()); - assertEquals("key: 0, itemId: " + CustomRenderer.ITEM_ID, + assertEquals("row: 0, key: 1", grid.getCell(0, 1).getText()); + assertEquals("key: 1, itemId: " + CustomRenderer.ITEM_ID, findDebugLabel().getText()); } diff --git a/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java b/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java index b38178b156..2e86053ef3 100644 --- a/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java @@ -43,6 +43,6 @@ public class JavaScriptRenderersTest extends MultiBrowserTest { // Verify onbrowserevent cell_1_1.click(); Assert.assertTrue(cell_1_1.getText().startsWith( - "Clicked 1 with key 1 at")); + "Clicked 1 with key 2 at")); } } -- cgit v1.2.3