diff options
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/com/vaadin/data/DataGenerator.java | 49 | ||||
-rw-r--r-- | server/src/com/vaadin/data/RpcDataProviderExtension.java | 223 | ||||
-rw-r--r-- | server/src/com/vaadin/ui/AbstractFocusable.java | 134 | ||||
-rw-r--r-- | server/src/com/vaadin/ui/Button.java | 105 | ||||
-rw-r--r-- | server/src/com/vaadin/ui/Grid.java | 579 |
5 files changed, 793 insertions, 297 deletions
diff --git a/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java new file mode 100644 index 0000000000..f025623a3e --- /dev/null +++ b/server/src/com/vaadin/data/DataGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.data; + +import java.io.Serializable; + +import com.vaadin.ui.Grid.AbstractGridExtension; +import com.vaadin.ui.Grid.AbstractRenderer; + +import elemental.json.JsonObject; + +/** + * Interface for {@link AbstractGridExtension}s that allows adding data to row + * objects being sent to client by the {@link RpcDataProviderExtension}. + * <p> + * {@link AbstractRenderer} implements this interface to provide encoded data to + * client for {@link Renderer}s automatically. + * + * @since + * @author Vaadin Ltd + */ +public interface DataGenerator extends Serializable { + + /** + * Adds data to row object for given item and item id being sent to client. + * + * @param itemId + * item id of item + * @param item + * item being sent to client + * @param rowData + * row object being sent to client + */ + public void generateData(Object itemId, Item item, JsonObject rowData); + +} diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 98394c45df..d81d024d72 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -23,11 +23,8 @@ 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.Set; -import java.util.logging.Level; -import java.util.logging.Logger; import com.google.gwt.thirdparty.guava.common.collect.BiMap; import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; @@ -43,10 +40,8 @@ import com.vaadin.data.Container.ItemSetChangeNotifier; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Property.ValueChangeNotifier; -import com.vaadin.data.util.converter.Converter; import com.vaadin.server.AbstractExtension; import com.vaadin.server.ClientConnector; -import com.vaadin.server.KeyMapper; import com.vaadin.shared.data.DataProviderRpc; import com.vaadin.shared.data.DataRequestRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -54,13 +49,9 @@ import com.vaadin.shared.ui.grid.GridState; import com.vaadin.shared.ui.grid.Range; import com.vaadin.ui.Component; import com.vaadin.ui.Grid; -import com.vaadin.ui.Grid.CellReference; -import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.Grid.DetailsGenerator; import com.vaadin.ui.Grid.RowReference; -import com.vaadin.ui.Grid.RowStyleGenerator; -import com.vaadin.ui.renderers.Renderer; import elemental.json.Json; import elemental.json.JsonArray; @@ -91,7 +82,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<Object, String> itemIdToKey = HashBiMap.create(); private Set<Object> pinnedItemIds = new HashSet<Object>(); private long rollingIndex = 0; @@ -122,6 +113,9 @@ public class RpcDataProviderExtension extends AbstractExtension { for (Object itemId : itemSet) { itemIdToKey.put(itemId, getKey(itemId)); + if (visibleDetails.contains(itemId)) { + detailComponentManager.createDetails(itemId); + } } } @@ -273,6 +267,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)); + } } /** @@ -594,7 +598,8 @@ public class RpcDataProviderExtension extends AbstractExtension { * @since 7.5.0 * @author Vaadin Ltd */ - public static final class DetailComponentManager implements Serializable { + // TODO this should probably be a static nested class + public final class DetailComponentManager implements DataGenerator { /** * This map represents all the components that have been requested for * each item id. @@ -602,9 +607,8 @@ public class RpcDataProviderExtension extends AbstractExtension { * Normally this map is consistent with what is displayed in the * component hierarchy (and thus the DOM). The only time this map is out * of sync with the DOM is between the any calls to - * {@link #createDetails(Object, int)} or - * {@link #destroyDetails(Object)}, and - * {@link GridClientRpc#setDetailsConnectorChanges(Set)}. + * {@link #createDetails(Object)} or {@link #destroyDetails(Object)}, + * and {@link GridClientRpc#setDetailsConnectorChanges(Set)}. * <p> * This is easily checked: if {@link #unattachedComponents} is * {@link Collection#isEmpty() empty}, then this field is consistent @@ -615,7 +619,7 @@ public class RpcDataProviderExtension extends AbstractExtension { /** * Keeps tabs on all the details that did not get a component during - * {@link #createDetails(Object, int)}. + * {@link #createDetails(Object)}. */ private final Set<Object> emptyDetails = Sets.newHashSet(); @@ -720,6 +724,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; @@ -807,14 +823,9 @@ public class RpcDataProviderExtension extends AbstractExtension { private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper(); - private KeyMapper<Object> columnKeys; - /** RpcDataProvider should send the current cache again. */ private boolean refreshCache = false; - private RowReference rowReference; - private CellReference cellReference; - /** Set of updated item ids */ private Set<Object> updatedItemIds = new LinkedHashSet<Object>(); @@ -831,10 +842,13 @@ public class RpcDataProviderExtension extends AbstractExtension { * This map represents all the details that are user-defined as visible. * This does not reflect the status in the DOM. */ + // TODO this should probably be inside DetailComponentManager private Set<Object> visibleDetails = new HashSet<Object>(); private final DetailComponentManager detailComponentManager = new DetailComponentManager(); + private Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>(); + /** * Creates a new data provider using the given container. * @@ -874,6 +888,8 @@ public class RpcDataProviderExtension extends AbstractExtension { .addItemSetChangeListener(itemListener); } + addDataGenerator(keyMapper); + addDataGenerator(detailComponentManager); } /** @@ -958,80 +974,14 @@ public class RpcDataProviderExtension extends AbstractExtension { private JsonValue getRowData(Collection<Column> columns, Object itemId) { Item item = container.getItem(itemId); - JsonObject rowData = Json.createObject(); - - Grid grid = getGrid(); - - for (Column column : columns) { - Object propertyId = column.getPropertyId(); - - Object propertyValue = item.getItemProperty(propertyId).getValue(); - JsonValue encodedValue = encodeValue(propertyValue, - column.getRenderer(), column.getConverter(), - grid.getLocale()); - - rowData.put(columnKeys.key(propertyId), encodedValue); - } - final JsonObject rowObject = Json.createObject(); - rowObject.put(GridState.JSONKEY_DATA, rowData); - rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId)); - - if (visibleDetails.contains(itemId)) { - // Double check to be sure details component exists. - detailComponentManager.createDetails(itemId); - Component detailsComponent = detailComponentManager.visibleDetailsComponents - .get(itemId); - rowObject.put( - GridState.JSONKEY_DETAILS_VISIBLE, - (detailsComponent != null ? detailsComponent - .getConnectorId() : "")); - } - - rowReference.set(itemId); - - CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator(); - if (cellStyleGenerator != null) { - setGeneratedCellStyles(cellStyleGenerator, rowObject, columns); - } - RowStyleGenerator rowStyleGenerator = grid.getRowStyleGenerator(); - if (rowStyleGenerator != null) { - setGeneratedRowStyles(rowStyleGenerator, rowObject); + for (DataGenerator dg : dataGenerators) { + dg.generateData(itemId, item, rowObject); } return rowObject; } - private void setGeneratedCellStyles(CellStyleGenerator generator, - JsonObject rowObject, Collection<Column> columns) { - JsonObject cellStyles = null; - for (Column column : columns) { - Object propertyId = column.getPropertyId(); - cellReference.set(propertyId); - String style = generator.getStyle(cellReference); - if (style != null && !style.isEmpty()) { - if (cellStyles == null) { - cellStyles = Json.createObject(); - } - - String columnKey = columnKeys.key(propertyId); - cellStyles.put(columnKey, style); - } - } - if (cellStyles != null) { - rowObject.put(GridState.JSONKEY_CELLSTYLES, cellStyles); - } - - } - - private void setGeneratedRowStyles(RowStyleGenerator generator, - JsonObject rowObject) { - String rowStyle = generator.getStyle(rowReference); - if (rowStyle != null && !rowStyle.isEmpty()) { - rowObject.put(GridState.JSONKEY_ROWSTYLE, rowStyle); - } - } - /** * Makes the data source available to the given {@link Grid} component. * @@ -1040,13 +990,38 @@ public class RpcDataProviderExtension extends AbstractExtension { * @param columnKeys * the key mapper for columns */ - public void extend(Grid component, KeyMapper<Object> columnKeys) { - this.columnKeys = columnKeys; + public void extend(Grid component) { detailComponentManager.setGrid(component); super.extend(component); } /** + * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}. + * DataGenerators are called when sending row data to client. If given + * DataGenerator is already added, this method does nothing. + * + * @since + * @param generator + * generator to add + */ + public void addDataGenerator(DataGenerator generator) { + dataGenerators.add(generator); + } + + /** + * Removes a {@link DataGenerator} from this + * {@code RpcDataProviderExtension}. If given DataGenerator is not added to + * this data provider, this method does nothing. + * + * @since + * @param generator + * generator to remove + */ + public void removeDataGenerator(DataGenerator generator) { + dataGenerators.remove(generator); + } + + /** * Informs the client side that new rows have been inserted into the data * source. * @@ -1152,11 +1127,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"); } @@ -1193,68 +1164,6 @@ public class RpcDataProviderExtension extends AbstractExtension { } /** - * Converts and encodes the given data model property value using the given - * converter and renderer. This method is public only for testing purposes. - * - * @param renderer - * the renderer to use - * @param converter - * the converter to use - * @param modelValue - * the value to convert and encode - * @param locale - * the locale to use in conversion - * @return an encoded value ready to be sent to the client - */ - public static <T> JsonValue encodeValue(Object modelValue, - Renderer<T> renderer, Converter<?, ?> converter, Locale locale) { - Class<T> presentationType = renderer.getPresentationType(); - T presentationValue; - - if (converter == null) { - try { - presentationValue = presentationType.cast(modelValue); - } catch (ClassCastException e) { - if (presentationType == String.class) { - // If there is no converter, just fallback to using - // toString(). - // modelValue can't be null as Class.cast(null) will always - // succeed - presentationValue = (T) modelValue.toString(); - } else { - throw new Converter.ConversionException( - "Unable to convert value of type " - + modelValue.getClass().getName() - + " to presentation type " - + presentationType.getName() - + ". No converter is set and the types are not compatible."); - } - } - } else { - assert presentationType.isAssignableFrom(converter - .getPresentationType()); - @SuppressWarnings("unchecked") - Converter<T, Object> safeConverter = (Converter<T, Object>) converter; - presentationValue = safeConverter.convertToPresentation(modelValue, - safeConverter.getPresentationType(), locale); - } - - JsonValue encodedValue; - try { - encodedValue = renderer.encode(presentationValue); - } catch (Exception e) { - getLogger().log(Level.SEVERE, "Unable to encode data", e); - encodedValue = renderer.encode(null); - } - - return encodedValue; - } - - private static Logger getLogger() { - return Logger.getLogger(RpcDataProviderExtension.class.getName()); - } - - /** * Marks a row's details to be visible or hidden. * <p> * If that row is currently in the client side's cache, this information diff --git a/server/src/com/vaadin/ui/AbstractFocusable.java b/server/src/com/vaadin/ui/AbstractFocusable.java new file mode 100644 index 0000000000..ad3b96f29b --- /dev/null +++ b/server/src/com/vaadin/ui/AbstractFocusable.java @@ -0,0 +1,134 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import com.vaadin.event.FieldEvents.BlurEvent; +import com.vaadin.event.FieldEvents.BlurListener; +import com.vaadin.event.FieldEvents.BlurNotifier; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusEvent; +import com.vaadin.event.FieldEvents.FocusListener; +import com.vaadin.event.FieldEvents.FocusNotifier; +import com.vaadin.shared.ui.TabIndexState; +import com.vaadin.ui.Component.Focusable; + +/** + * An abstract base class for focusable components. Includes API for setting the + * tab index, programmatic focusing, and adding focus and blur listeners. + * + * @since 7.6 + * @author Vaadin Ltd + */ +public abstract class AbstractFocusable extends AbstractComponent implements + Focusable, FocusNotifier, BlurNotifier { + + protected AbstractFocusable() { + registerRpc(new FocusAndBlurServerRpcImpl(this) { + @Override + protected void fireEvent(Event event) { + AbstractFocusable.this.fireEvent(event); + } + }); + } + + @Override + public void addBlurListener(BlurListener listener) { + addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, + BlurListener.blurMethod); + } + + /** + * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)} + */ + @Override + @Deprecated + public void addListener(BlurListener listener) { + addBlurListener(listener); + } + + @Override + public void removeBlurListener(BlurListener listener) { + removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #removeBlurListener(BlurListener)} + */ + @Override + @Deprecated + public void removeListener(BlurListener listener) { + removeBlurListener(listener); + + } + + @Override + public void addFocusListener(FocusListener listener) { + addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, + FocusListener.focusMethod); + + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #addFocusListener(FocusListener)} + */ + @Override + @Deprecated + public void addListener(FocusListener listener) { + addFocusListener(listener); + } + + @Override + public void removeFocusListener(FocusListener listener) { + removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #removeFocusListener(FocusListener)} + */ + @Override + @Deprecated + public void removeListener(FocusListener listener) { + removeFocusListener(listener); + } + + @Override + public void focus() { + super.focus(); + } + + @Override + public int getTabIndex() { + return getState(false).tabIndex; + } + + @Override + public void setTabIndex(int tabIndex) { + getState().tabIndex = tabIndex; + } + + @Override + protected TabIndexState getState() { + return (TabIndexState) super.getState(); + } + + @Override + protected TabIndexState getState(boolean markAsDirty) { + return (TabIndexState) super.getState(markAsDirty); + } +} diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java index 6beb6ed686..a918780a60 100644 --- a/server/src/com/vaadin/ui/Button.java +++ b/server/src/com/vaadin/ui/Button.java @@ -24,12 +24,7 @@ import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; import com.vaadin.event.Action; -import com.vaadin.event.FieldEvents; -import com.vaadin.event.FieldEvents.BlurEvent; -import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; -import com.vaadin.event.FieldEvents.FocusEvent; -import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutAction.KeyCode; import com.vaadin.event.ShortcutAction.ModifierKey; @@ -38,7 +33,6 @@ import com.vaadin.server.Resource; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.ui.button.ButtonServerRpc; import com.vaadin.shared.ui.button.ButtonState; -import com.vaadin.ui.Component.Focusable; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.util.ReflectTools; @@ -50,8 +44,7 @@ import com.vaadin.util.ReflectTools; * @since 3.0 */ @SuppressWarnings("serial") -public class Button extends AbstractComponent implements - FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Focusable, +public class Button extends AbstractFocusable implements Action.ShortcutNotifier { private ButtonServerRpc rpc = new ButtonServerRpc() { @@ -72,20 +65,11 @@ public class Button extends AbstractComponent implements } }; - FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) { - - @Override - protected void fireEvent(Event event) { - Button.this.fireEvent(event); - } - }; - /** * Creates a new push button. */ public Button() { registerRpc(rpc); - registerRpc(focusBlurRpc); } /** @@ -393,67 +377,6 @@ public class Button extends AbstractComponent implements fireEvent(new Button.ClickEvent(this, details)); } - @Override - public void addBlurListener(BlurListener listener) { - addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, - BlurListener.blurMethod); - } - - /** - * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)} - **/ - @Override - @Deprecated - public void addListener(BlurListener listener) { - addBlurListener(listener); - } - - @Override - public void removeBlurListener(BlurListener listener) { - removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeBlurListener(BlurListener)} - **/ - @Override - @Deprecated - public void removeListener(BlurListener listener) { - removeBlurListener(listener); - } - - @Override - public void addFocusListener(FocusListener listener) { - addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, - FocusListener.focusMethod); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addFocusListener(FocusListener)} - **/ - @Override - @Deprecated - public void addListener(FocusListener listener) { - addFocusListener(listener); - } - - @Override - public void removeFocusListener(FocusListener listener) { - removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeFocusListener(FocusListener)} - **/ - @Override - @Deprecated - public void removeListener(FocusListener listener) { - removeFocusListener(listener); - } - /* * Actions */ @@ -575,32 +498,6 @@ public class Button extends AbstractComponent implements getState().disableOnClick = disableOnClick; } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Component.Focusable#getTabIndex() - */ - @Override - public int getTabIndex() { - return getState(false).tabIndex; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) - */ - @Override - public void setTabIndex(int tabIndex) { - getState().tabIndex = tabIndex; - } - - @Override - public void focus() { - // Overridden only to make public - super.focus(); - } - @Override protected ButtonState getState() { return (ButtonState) super.getState(); diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 3e4a78db9d..c7ad9632fa 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -45,10 +46,14 @@ import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView; import com.vaadin.data.Container; import com.vaadin.data.Container.Indexed; +import com.vaadin.data.Container.ItemSetChangeEvent; +import com.vaadin.data.Container.ItemSetChangeListener; +import com.vaadin.data.Container.ItemSetChangeNotifier; import com.vaadin.data.Container.PropertySetChangeEvent; import com.vaadin.data.Container.PropertySetChangeListener; import com.vaadin.data.Container.PropertySetChangeNotifier; import com.vaadin.data.Container.Sortable; +import com.vaadin.data.DataGenerator; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.RpcDataProviderExtension; @@ -172,7 +177,7 @@ import elemental.json.JsonValue; * @since 7.4 * @author Vaadin Ltd */ -public class Grid extends AbstractComponent implements SelectionNotifier, +public class Grid extends AbstractFocusable implements SelectionNotifier, SortNotifier, SelectiveRenderer, ItemClickNotifier { /** @@ -511,6 +516,100 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** + * Interface for an editor event listener + */ + public interface EditorListener extends Serializable { + + public static final Method EDITOR_OPEN_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorOpened", + EditorOpenEvent.class); + public static final Method EDITOR_MOVE_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorMoved", + EditorMoveEvent.class); + public static final Method EDITOR_CLOSE_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorClosed", + EditorCloseEvent.class); + + /** + * Called when an editor is opened + * + * @param e + * an editor open event object + */ + public void editorOpened(EditorOpenEvent e); + + /** + * Called when an editor is reopened without closing it first + * + * @param e + * an editor move event object + */ + public void editorMoved(EditorMoveEvent e); + + /** + * Called when an editor is closed + * + * @param e + * an editor close event object + */ + public void editorClosed(EditorCloseEvent e); + + } + + /** + * Base class for editor related events + */ + public static abstract class EditorEvent extends Component.Event { + + private Object itemID; + + protected EditorEvent(Grid source, Object itemID) { + super(source); + this.itemID = itemID; + } + + /** + * Get the item (row) for which this editor was opened + */ + public Object getItem() { + return itemID; + } + + } + + /** + * This event gets fired when an editor is opened + */ + public static class EditorOpenEvent extends EditorEvent { + + public EditorOpenEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** + * This event gets fired when an editor is opened while another row is being + * edited (i.e. editor focus moves elsewhere) + */ + public static class EditorMoveEvent extends EditorEvent { + + public EditorMoveEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** + * This event gets fired when an editor is dismissed or closed by other + * means. + */ + public static class EditorCloseEvent extends EditorEvent { + + public EditorCloseEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** * Default error handler for the editor * */ @@ -1432,39 +1531,174 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** - * Callback interface for generating custom style names for data rows + * A callback interface for generating custom style names for Grid rows. * * @see Grid#setRowStyleGenerator(RowStyleGenerator) */ public interface RowStyleGenerator extends Serializable { /** - * Called by Grid to generate a style name for a row + * Called by Grid to generate a style name for a row. * - * @param rowReference - * The row to generate a style for + * @param row + * the row to generate a style for * @return the style name to add to this row, or {@code null} to not set * any style */ - public String getStyle(RowReference rowReference); + public String getStyle(RowReference row); } /** - * Callback interface for generating custom style names for cells + * A callback interface for generating custom style names for Grid cells. * * @see Grid#setCellStyleGenerator(CellStyleGenerator) */ public interface CellStyleGenerator extends Serializable { /** - * Called by Grid to generate a style name for a column + * Called by Grid to generate a style name for a column. * - * @param cellReference - * The cell to generate a style for + * @param cell + * the cell to generate a style for * @return the style name to add to this cell, or {@code null} to not * set any style */ - public String getStyle(CellReference cellReference); + public String getStyle(CellReference cell); + } + + /** + * A callback interface for generating optional descriptions (tooltips) for + * Grid rows. If a description is generated for a row, it is used for all + * the cells in the row for which a {@link CellDescriptionGenerator cell + * description} is not generated. + * + * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator) + * + * @since + */ + public interface RowDescriptionGenerator extends Serializable { + + /** + * Called by Grid to generate a description (tooltip) for a row. The + * description may contain HTML which is rendered directly; if this is + * not desired the returned string must be escaped by the implementing + * method. + * + * @param row + * the row to generate a description for + * @return the row description or {@code null} for no description + */ + public String getDescription(RowReference row); + } + + /** + * A callback interface for generating optional descriptions (tooltips) for + * Grid cells. If a cell has both a {@link RowDescriptionGenerator row + * description} and a cell description, the latter has precedence. + * + * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator) + * + * @since + */ + public interface CellDescriptionGenerator extends Serializable { + + /** + * Called by Grid to generate a description (tooltip) for a cell. The + * description may contain HTML which is rendered directly; if this is + * not desired the returned string must be escaped by the implementing + * method. + * + * @param cell + * the cell to generate a description for + * @return the cell description or {@code null} for no description + */ + public String getDescription(CellReference cell); + } + + /** + * Class for generating all row and cell related data for the essential + * parts of Grid. + */ + private class RowDataGenerator implements DataGenerator { + + private void put(String key, String value, JsonObject object) { + if (value != null && !value.isEmpty()) { + object.put(key, value); + } + } + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + RowReference row = new RowReference(Grid.this); + row.set(itemId); + + if (rowStyleGenerator != null) { + String style = rowStyleGenerator.getStyle(row); + put(GridState.JSONKEY_ROWSTYLE, style, rowData); + } + + if (rowDescriptionGenerator != null) { + String description = rowDescriptionGenerator + .getDescription(row); + put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData); + + } + + JsonObject cellStyles = Json.createObject(); + JsonObject cellData = Json.createObject(); + JsonObject cellDescriptions = Json.createObject(); + + CellReference cell = new CellReference(row); + + for (Column column : getColumns()) { + cell.set(column.getPropertyId()); + + writeData(cell, cellData); + writeStyles(cell, cellStyles); + writeDescriptions(cell, cellDescriptions); + } + + if (cellDescriptionGenerator != null + && cellDescriptions.keys().length > 0) { + rowData.put(GridState.JSONKEY_CELLDESCRIPTION, cellDescriptions); + } + + if (cellStyleGenerator != null && cellStyles.keys().length > 0) { + rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles); + } + + rowData.put(GridState.JSONKEY_DATA, cellData); + } + + private void writeStyles(CellReference cell, JsonObject styles) { + if (cellStyleGenerator != null) { + String style = cellStyleGenerator.getStyle(cell); + put(columnKeys.key(cell.getPropertyId()), style, styles); + } + } + + private void writeDescriptions(CellReference cell, + JsonObject descriptions) { + if (cellDescriptionGenerator != null) { + String description = cellDescriptionGenerator + .getDescription(cell); + put(columnKeys.key(cell.getPropertyId()), description, + descriptions); + } + } + + private void writeData(CellReference cell, JsonObject data) { + Column column = getColumn(cell.getPropertyId()); + Converter<?, ?> converter = column.getConverter(); + Renderer<?> renderer = column.getRenderer(); + + Item item = cell.getItem(); + Object modelValue = item.getItemProperty(cell.getPropertyId()) + .getValue(); + + data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer + .encodeValue(modelValue, renderer, converter, getLocale())); + } } /** @@ -3382,10 +3616,78 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return JsonCodec.encode(value, null, type, getUI().getConnectorTracker()).getEncodedValue(); } + + /** + * Converts and encodes the given data model property value using the + * given converter and renderer. This method is public only for testing + * purposes. + * + * @param renderer + * the renderer to use + * @param converter + * the converter to use + * @param modelValue + * the value to convert and encode + * @param locale + * the locale to use in conversion + * @return an encoded value ready to be sent to the client + */ + public static <T> JsonValue encodeValue(Object modelValue, + Renderer<T> renderer, Converter<?, ?> converter, Locale locale) { + Class<T> presentationType = renderer.getPresentationType(); + T presentationValue; + + if (converter == null) { + try { + presentationValue = presentationType.cast(modelValue); + } catch (ClassCastException e) { + if (presentationType == String.class) { + // If there is no converter, just fallback to using + // toString(). modelValue can't be null as + // Class.cast(null) will always succeed + presentationValue = (T) modelValue.toString(); + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + modelValue.getClass().getName() + + " to presentation type " + + presentationType.getName() + + ". No converter is set and the types are not compatible."); + } + } + } else { + assert presentationType.isAssignableFrom(converter + .getPresentationType()); + @SuppressWarnings("unchecked") + Converter<T, Object> safeConverter = (Converter<T, Object>) converter; + presentationValue = safeConverter + .convertToPresentation(modelValue, + safeConverter.getPresentationType(), locale); + } + + JsonValue encodedValue; + try { + encodedValue = renderer.encode(presentationValue); + } catch (Exception e) { + getLogger().log(Level.SEVERE, "Unable to encode data", e); + encodedValue = renderer.encode(null); + } + + return encodedValue; + } + + private static Logger getLogger() { + return Logger.getLogger(AbstractRenderer.class.getName()); + } + } /** * An abstract base class for server-side Grid extensions. + * <p> + * Note: If the extension is an instance of {@link DataGenerator} it will + * automatically register itself to {@link RpcDataProviderExtension} of + * extended Grid. On remove this registration is automatically removed. * * @since 7.5 */ @@ -3410,6 +3712,26 @@ public class Grid extends AbstractComponent implements SelectionNotifier, extend(grid); } + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + + if (this instanceof DataGenerator) { + getParentGrid().datasourceExtension + .addDataGenerator((DataGenerator) this); + } + } + + @Override + public void remove() { + if (this instanceof DataGenerator) { + getParentGrid().datasourceExtension + .removeDataGenerator((DataGenerator) this); + } + + super.remove(); + } + /** * Gets the item id for a row key. * <p> @@ -3451,9 +3773,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier, if (getParent() instanceof Grid) { Grid grid = (Grid) getParent(); return grid; + } else if (getParent() == null) { + throw new IllegalStateException( + "Renderer is not attached to any parent"); } else { throw new IllegalStateException( - "Renderers can be used only with Grid"); + "Renderers can be used only with Grid. Extended " + + getParent().getClass().getSimpleName() + + " instead"); } } } @@ -3531,6 +3858,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } }; + private final ItemSetChangeListener editorClosingItemSetListener = new ItemSetChangeListener() { + @Override + public void containerItemSetChange(ItemSetChangeEvent event) { + cancelEditor(); + } + }; + private RpcDataProviderExtension datasourceExtension; /** @@ -3556,6 +3890,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private CellStyleGenerator cellStyleGenerator; private RowStyleGenerator rowStyleGenerator; + private CellDescriptionGenerator cellDescriptionGenerator; + private RowDescriptionGenerator rowDescriptionGenerator; + /** * <code>true</code> if Grid is using the internal IndexedContainer created * in Grid() constructor, or <code>false</code> if the user has set their @@ -3873,34 +4210,61 @@ public class Grid extends AbstractComponent implements SelectionNotifier, userOriginated); } } + + @Override + public void editorOpen(String rowKey) { + fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper() + .getItemId(rowKey))); + } + + @Override + public void editorMove(String rowKey) { + fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper() + .getItemId(rowKey))); + } + + @Override + public void editorClose(String rowKey) { + fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper() + .getItemId(rowKey))); + } }); registerRpc(new EditorServerRpc() { @Override public void bind(int rowIndex) { - Exception exception = null; try { Object id = getContainerDataSource().getIdByIndex(rowIndex); - if (editedItemId == null) { - editedItemId = id; - } - if (editedItemId.equals(id)) { - doEditItem(); + final boolean opening = editedItemId == null; + + final boolean moving = !opening && !editedItemId.equals(id); + + final boolean allowMove = !isEditorBuffered() + && getEditorFieldGroup().isValid(); + + if (opening || !moving || allowMove) { + doBind(id); + } else { + failBind(null); } } catch (Exception e) { - exception = e; + failBind(e); } + } - if (exception != null) { - handleError(exception); - doCancelEditor(); - getEditorRpc().confirmBind(false); - } else { - doEditItem(); - getEditorRpc().confirmBind(true); + private void doBind(Object id) { + editedItemId = id; + doEditItem(); + getEditorRpc().confirmBind(true); + } + + private void failBind(Exception e) { + if (e != null) { + handleError(e); } + getEditorRpc().confirmBind(false); } @Override @@ -4009,10 +4373,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, removeExtension(datasourceExtension); } - datasource = container; - resetEditor(); + datasource = container; + // // Adjust sort order // @@ -4041,7 +4405,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } datasourceExtension = new RpcDataProviderExtension(container); - datasourceExtension.extend(this, columnKeys); + datasourceExtension.extend(this); + datasourceExtension.addDataGenerator(new RowDataGenerator()); detailComponentManager = datasourceExtension .getDetailComponentManager(); @@ -4059,6 +4424,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, ((PropertySetChangeNotifier) datasource) .addPropertySetChangeListener(propertyListener); } + /* * activeRowHandler will be updated by the client-side request that * occurs on container change - no need to actively re-insert any @@ -5499,6 +5865,73 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** + * Sets the {@code CellDescriptionGenerator} instance for generating + * optional descriptions (tooltips) for individual Grid cells. If a + * {@link RowDescriptionGenerator} is also set, the row description it + * generates is displayed for cells for which {@code generator} returns + * null. + * + * @param generator + * the description generator to use or {@code null} to remove a + * previously set generator if any + * + * @see #setRowDescriptionGenerator(RowDescriptionGenerator) + * + * @since + */ + public void setCellDescriptionGenerator(CellDescriptionGenerator generator) { + cellDescriptionGenerator = generator; + getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null); + datasourceExtension.refreshCache(); + } + + /** + * Returns the {@code CellDescriptionGenerator} instance used to generate + * descriptions (tooltips) for Grid cells. + * + * @return the description generator or {@code null} if no generator is set + * + * @since + */ + public CellDescriptionGenerator getCellDescriptionGenerator() { + return cellDescriptionGenerator; + } + + /** + * Sets the {@code RowDescriptionGenerator} instance for generating optional + * descriptions (tooltips) for Grid rows. If a + * {@link CellDescriptionGenerator} is also set, the row description + * generated by {@code generator} is used for cells for which the cell + * description generator returns null. + * + * + * @param generator + * the description generator to use or {@code null} to remove a + * previously set generator if any + * + * @see #setCellDescriptionGenerator(CellDescriptionGenerator) + * + * @since + */ + public void setRowDescriptionGenerator(RowDescriptionGenerator generator) { + rowDescriptionGenerator = generator; + getState().hasDescriptions = (generator != null || cellDescriptionGenerator != null); + datasourceExtension.refreshCache(); + } + + /** + * Returns the {@code RowDescriptionGenerator} instance used to generate + * descriptions (tooltips) for Grid rows + * + * @return the description generator or {@code} null if no generator is set + * + * @since + */ + public RowDescriptionGenerator getRowDescriptionGenerator() { + return rowDescriptionGenerator; + } + + /** * Sets the style generator that is used for generating styles for cells * * @param cellStyleGenerator @@ -5507,8 +5940,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) { this.cellStyleGenerator = cellStyleGenerator; - getState().hasCellStyleGenerator = (cellStyleGenerator != null); - datasourceExtension.refreshCache(); } @@ -5531,8 +5962,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { this.rowStyleGenerator = rowStyleGenerator; - getState().hasRowStyleGenerator = (rowStyleGenerator != null); - datasourceExtension.refreshCache(); } @@ -5742,10 +6171,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Opens the editor interface for the provided item. Scrolls the Grid to * bring the item to view if it is not already visible. * + * Note that any cell content rendered by a WidgetRenderer will not be + * visible in the editor row. + * * @param itemId * the id of the item to edit * @throws IllegalStateException - * if the editor is not enabled or already editing an item + * if the editor is not enabled or already editing an item in + * buffered mode * @throws IllegalArgumentException * if the {@code itemId} is not in the backing container * @see #setEditorEnabled(boolean) @@ -5754,8 +6187,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, IllegalArgumentException { if (!isEditorEnabled()) { throw new IllegalStateException("Item editor is not enabled"); - } else if (editedItemId != null) { - throw new IllegalStateException("Editing item + " + itemId + } else if (isEditorBuffered() && editedItemId != null) { + throw new IllegalStateException("Editing item " + itemId + " failed. Item editor is already editing item " + editedItemId); } else if (!getContainerDataSource().containsId(itemId)) { @@ -5783,6 +6216,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, f.markAsDirtyRecursive(); } + if (datasource instanceof ItemSetChangeNotifier) { + ((ItemSetChangeNotifier) datasource) + .addItemSetChangeListener(editorClosingItemSetListener); + } } private void setEditorField(Object propertyId, Field<?> field) { @@ -5832,6 +6269,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier, editorFieldGroup.discard(); editorFieldGroup.setItemDataSource(null); + if (datasource instanceof ItemSetChangeNotifier) { + ((ItemSetChangeNotifier) datasource) + .removeItemSetChangeListener(editorClosingItemSetListener); + } + // Mark Grid as dirty so the client side gets to know that the editors // are no longer attached markAsDirty(); @@ -5981,6 +6423,70 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return getState(false).editorCancelCaption; } + /** + * Add an editor event listener + * + * @param listener + * the event listener object to add + */ + public void addEditorListener(EditorListener listener) { + addListener(GridConstants.EDITOR_OPEN_EVENT_ID, EditorOpenEvent.class, + listener, EditorListener.EDITOR_OPEN_METHOD); + addListener(GridConstants.EDITOR_MOVE_EVENT_ID, EditorMoveEvent.class, + listener, EditorListener.EDITOR_MOVE_METHOD); + addListener(GridConstants.EDITOR_CLOSE_EVENT_ID, + EditorCloseEvent.class, listener, + EditorListener.EDITOR_CLOSE_METHOD); + } + + /** + * Remove an editor event listener + * + * @param listener + * the event listener object to remove + */ + public void removeEditorListener(EditorListener listener) { + removeListener(GridConstants.EDITOR_OPEN_EVENT_ID, + EditorOpenEvent.class, listener); + removeListener(GridConstants.EDITOR_MOVE_EVENT_ID, + EditorMoveEvent.class, listener); + removeListener(GridConstants.EDITOR_CLOSE_EVENT_ID, + EditorCloseEvent.class, listener); + } + + /** + * Sets the buffered editor mode. The default mode is buffered ( + * <code>true</code>). + * + * @since 7.6 + * @param editorBuffered + * <code>true</code> to enable buffered editor, + * <code>false</code> to disable it + * @throws IllegalStateException + * If editor is active while attempting to change the buffered + * mode. + */ + public void setEditorBuffered(boolean editorBuffered) + throws IllegalStateException { + if (isEditorActive()) { + throw new IllegalStateException( + "Can't change editor unbuffered mode while editor is active."); + } + getState().editorBuffered = editorBuffered; + editorFieldGroup.setBuffered(editorBuffered); + } + + /** + * Gets the buffered editor mode. + * + * @since 7.6 + * @return <code>true</code> if buffered editor is enabled, + * <code>false</code> otherwise + */ + public boolean isEditorBuffered() { + return getState().editorBuffered; + } + @Override public void addItemClickListener(ItemClickListener listener) { addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, @@ -6273,6 +6779,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, result.add("footer-visible"); result.add("editor-error-handler"); result.add("height-mode"); + return result; } } |