summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2015-09-04 15:05:27 +0300
committerArtur Signell <artur@vaadin.com>2015-09-04 15:05:27 +0300
commitf46be1b2792755cb7d9b111068dd6cf202398c4a (patch)
tree74f7098faee688cc1f1ca1b6ffe9e912338bbcaf /server
parente603ea3cf91e5dd0893b766f27d400947527fba9 (diff)
parentebceef4d44bcd61605fa92fcf7be8d3678537599 (diff)
downloadvaadin-framework-f46be1b2792755cb7d9b111068dd6cf202398c4a.tar.gz
vaadin-framework-f46be1b2792755cb7d9b111068dd6cf202398c4a.zip
Merge remote-tracking branch 'origin/master' into reconnect-dialog
Change-Id: Ie622160a83116c83b255a26bec297f73f3223ac7
Diffstat (limited to 'server')
-rw-r--r--server/build.xml2
-rw-r--r--server/ivy.xml2
-rw-r--r--server/src/com/vaadin/data/DataGenerator.java49
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java958
-rw-r--r--server/src/com/vaadin/data/util/ContainerOrderedWrapper.java24
-rw-r--r--server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java83
-rw-r--r--server/src/com/vaadin/event/ShortcutAction.java30
-rw-r--r--server/src/com/vaadin/server/Constants.java1
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java20
-rw-r--r--server/src/com/vaadin/server/WebBrowser.java14
-rw-r--r--server/src/com/vaadin/server/communication/AtmospherePushConnection.java37
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java2
-rw-r--r--server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java9
-rw-r--r--server/src/com/vaadin/server/communication/PushHandler.java41
-rw-r--r--server/src/com/vaadin/server/communication/PushRequestHandler.java25
-rw-r--r--server/src/com/vaadin/ui/AbstractFocusable.java134
-rw-r--r--server/src/com/vaadin/ui/Button.java105
-rw-r--r--server/src/com/vaadin/ui/Grid.java924
-rw-r--r--server/src/com/vaadin/ui/HorizontalLayout.java7
-rw-r--r--server/src/com/vaadin/ui/Table.java90
-rw-r--r--server/src/com/vaadin/ui/VerticalLayout.java7
-rw-r--r--server/src/com/vaadin/ui/Window.java238
-rw-r--r--server/src/com/vaadin/ui/renderers/ImageRenderer.java2
-rw-r--r--server/src/com/vaadin/ui/themes/ValoTheme.java2
-rw-r--r--server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java153
-rw-r--r--server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java29
-rw-r--r--server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java20
-rw-r--r--server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java102
-rw-r--r--server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java27
-rw-r--r--server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java120
-rw-r--r--server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java34
-rw-r--r--server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java49
-rw-r--r--server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java4
-rw-r--r--server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java2
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java2
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java88
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java39
-rw-r--r--server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java26
38 files changed, 2107 insertions, 1394 deletions
diff --git a/server/build.xml b/server/build.xml
index 4bb2bde730..95c0b0add4 100644
--- a/server/build.xml
+++ b/server/build.xml
@@ -34,7 +34,7 @@
</target>
<target name="jar" depends="compress-files">
<property name="server.osgi.import"
- value="javax.servlet;version=&quot;2.4.0&quot;,javax.servlet.http;version=&quot;2.4.0&quot;,javax.validation;version=&quot;1.0.0.GA&quot;;resolution:=optional,org.jsoup;version=&quot;1.6.3&quot;,org.jsoup.parser;version=&quot;1.6.3&quot;,org.jsoup.nodes;version=&quot;1.6.3&quot;,org.jsoup.helper;version=&quot;1.6.3&quot;,org.jsoup.safety;version=&quot;1.6.3&quot;,org.jsoup.select;version=&quot;1.6.3&quot;" />
+ value="javax.servlet;version=&quot;2.4.0&quot;,javax.servlet.http;version=&quot;2.4.0&quot;,javax.validation;version=&quot;1.0.0.GA&quot;;resolution:=optional,org.jsoup;version=&quot;1.6.3&quot;,org.jsoup.parser;version=&quot;1.6.3&quot;,org.jsoup.nodes;version=&quot;1.6.3&quot;,org.jsoup.helper;version=&quot;1.6.3&quot;,org.jsoup.safety;version=&quot;1.6.3&quot;,org.jsoup.select;version=&quot;1.6.3&quot;,javax.portlet;version=&quot;[2.0,3)&quot;;resolution:=optional,javax.portlet.filter;version=&quot;[2.0,3)&quot;;resolution:=optional,com.liferay.portal.kernel.util;resolution:=optional" />
<property name="server.osgi.require"
value="com.google.gwt.thirdparty.guava;bundle-version=&quot;16.0.1.vaadin1&quot;,com.vaadin.shared;bundle-version=&quot;${vaadin.version}&quot;,com.vaadin.push;bundle-version=&quot;${vaadin.version}&quot;;resolution:=optional,com.vaadin.sass-compiler;bundle-version=&quot;${vaadin.sass.version}&quot;;resolution:=optional" />
<antcall target="common.jar">
diff --git a/server/ivy.xml b/server/ivy.xml
index e9bc8b818d..0711b4b2ea 100644
--- a/server/ivy.xml
+++ b/server/ivy.xml
@@ -36,7 +36,7 @@
<!-- Google App Engine -->
<dependency org="com.google.appengine" name="appengine-api-1.0-sdk"
- rev="1.2.1" conf="build-provided,ide,test -> default" />
+ rev="1.7.7" conf="build-provided,ide,test -> default" />
<!-- Bean Validation API -->
<dependency org="javax.validation" name="validation-api"
diff --git a/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java
new file mode 100644
index 0000000000..a5333b8523
--- /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 7.6
+ * @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 b3c7972b52..78c87ab23d 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -23,14 +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;
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,31 +38,23 @@ 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;
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;
-import com.vaadin.ui.Grid.CellStyleGenerator;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.Grid.DetailsGenerator;
import com.vaadin.ui.Grid.RowReference;
-import com.vaadin.ui.Grid.RowStyleGenerator;
-import com.vaadin.ui.renderers.Renderer;
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.JsonObject;
-import elemental.json.JsonValue;
/**
* Provides Vaadin server-side container data source to a
@@ -82,445 +69,84 @@ import elemental.json.JsonValue;
public class RpcDataProviderExtension extends AbstractExtension {
/**
- * ItemId to Key to ItemId mapper.
- * <p>
- * 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.
- * <p>
- * <em>Technical note:</em> This class also keeps tabs on which indices are
- * being shown/selected, and is able to clean up after itself once the
- * itemId &lrarr; key mapping is not needed anymore. In other words, this
- * doesn't leak memory.
+ * Class for keeping track of current items and ValueChangeListeners.
+ *
+ * @since 7.6
*/
- public class DataProviderKeyMapper implements Serializable {
- private final BiMap<Object, String> itemIdToKey = HashBiMap.create();
- private Set<Object> pinnedItemIds = new HashSet<Object>();
- private long rollingIndex = 0;
-
- private DataProviderKeyMapper() {
- // 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<Object> itemSet = new HashSet<Object>(itemIds);
- Set<Object> itemsRemoved = new HashSet<Object>();
- for (Object itemId : itemIdToKey.keySet()) {
- if (!itemSet.contains(itemId) && !isPinned(itemId)) {
- itemsRemoved.add(itemId);
- }
- }
-
- 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));
- }
- }
- }
-
- private String nextKey() {
- return String.valueOf(rollingIndex++);
- }
+ private class ActiveItemHandler implements Serializable, DataGenerator {
- /**
- * 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;
- }
+ private final Map<Object, GridValueChangeListener> activeItemMap = new HashMap<Object, GridValueChangeListener>();
+ private final KeyMapper<Object> keyMapper = new KeyMapper<Object>();
+ private final Set<Object> droppedItems = new HashSet<Object>();
/**
- * Gets keys for a collection of item ids.
+ * Registers ValueChangeListeners for given item ids.
* <p>
- * If the itemIds are currently cached, the existing keys will be used.
- * Otherwise new ones will be created.
+ * Note: This method will clean up any unneeded listeners and key
+ * mappings
*
* @param itemIds
- * the item ids for which to get keys
- * @return keys for the {@code itemIds}
+ * collection of new active item ids
*/
- public List<String> getKeys(Collection<Object> itemIds) {
- if (itemIds == null) {
- throw new IllegalArgumentException("itemIds can't be null");
- }
-
- ArrayList<String> keys = new ArrayList<String>(itemIds.size());
+ public void addActiveItems(Collection<?> itemIds) {
for (Object itemId : itemIds) {
- keys.add(getKey(itemId));
- }
- return keys;
- }
-
- /**
- * Gets the registered item id based on its key.
- * <p>
- * 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<Object> getItemIds(Collection<String> keys)
- throws IllegalStateException {
- if (keys == null) {
- throw new IllegalArgumentException("keys may not be null");
- }
-
- ArrayList<Object> itemIds = new ArrayList<Object>(keys.size());
- for (String key : keys) {
- itemIds.add(getItemId(key));
- }
- return itemIds;
- }
-
- /**
- * Pin an item id to be cached indefinitely.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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");
+ if (!activeItemMap.containsKey(itemId)) {
+ activeItemMap.put(itemId, new GridValueChangeListener(
+ itemId, container.getItem(itemId)));
+ }
}
- pinnedItemIds.remove(itemId);
+ // Remove still active rows that were "dropped"
+ droppedItems.removeAll(itemIds);
+ internalDropActiveItems(droppedItems);
+ droppedItems.clear();
}
/**
- * Checks whether an item id is pinned or not.
+ * Marks given item id as dropped. Dropped items are cleared when adding
+ * new active items.
*
* @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);
- }
- }
-
- /**
- * 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).
- * <p>
- * This bookeeping includes, but is not limited to:
- * <ul>
- * <li>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
- * <li>attaching and detaching {@link com.vaadin.ui.Component Components}
- * from the Vaadin Component hierarchy.
- * </ul>
- */
- private class ActiveRowHandler implements Serializable {
- /**
- * A map from index to the value change listener used for all of column
- * properties
- */
- private final Map<Integer, GridValueChangeListener> valueChangeListeners = new HashMap<Integer, GridValueChangeListener>();
-
- /**
- * 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".
- * <p>
- * "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).
- *
- * @param firstActiveRow
- * the first active row
- * @param activeRowCount
- * the number of active rows
- */
- 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);
- }
- }
-
- private void removeValueChangeListeners(Range range) {
- for (Integer i = range.getStart(); i < range.getEnd(); i++) {
- final GridValueChangeListener listener = valueChangeListeners
- .remove(i);
-
- assert listener != null : "Trying to remove nonexisting listener";
-
- listener.removeListener();
- }
- }
-
- /**
- * Manages removed columns in active rows.
- * <p>
- * This method does <em>not</em> send data again to the client.
- *
- * @param removedColumns
- * the columns that have been removed from the grid
+ * dropped item id
*/
- public void columnsRemoved(Collection<Column> removedColumns) {
- if (removedColumns.isEmpty()) {
- return;
- }
-
- for (GridValueChangeListener listener : valueChangeListeners
- .values()) {
- listener.removeColumns(removedColumns);
+ public void dropActiveItem(Object itemId) {
+ if (activeItemMap.containsKey(itemId)) {
+ droppedItems.add(itemId);
}
}
- /**
- * Manages added columns in active rows.
- * <p>
- * This method sends the data for the changed rows to client side.
- *
- * @param addedColumns
- * the columns that have been added to the grid
- */
- public void columnsAdded(Collection<Column> addedColumns) {
- if (addedColumns.isEmpty()) {
- return;
- }
+ private void internalDropActiveItems(Collection<Object> itemIds) {
+ for (Object itemId : droppedItems) {
+ assert activeItemMap.containsKey(itemId) : "Item ID should exist in the activeItemMap";
- for (GridValueChangeListener listener : valueChangeListeners
- .values()) {
- listener.addColumns(addedColumns);
+ activeItemMap.remove(itemId).removeListener();
+ keyMapper.remove(itemId);
}
}
/**
- * Handles the insertion of rows.
- * <p>
- * This method's responsibilities are to:
- * <ul>
- * <li>shift the internal bookkeeping by <code>count</code> if the
- * insertion happens above currently active range
- * <li>ignore rows inserted below the currently active range
- * <li>shift (and deactivate) rows pushed out of view
- * <li>activate rows that are inserted in the current viewport
- * </ul>
+ * 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 <code>firstIndex</code>
+ * @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<Object> getActiveItemIds() {
+ return new HashSet<Object>(activeItemMap.keySet());
}
/**
- * Handles the removal of rows.
- * <p>
- * This method's responsibilities are to:
- * <ul>
- * <li>shift the internal bookkeeping by <code>count</code> if the
- * removal happens above currently active range
- * <li>ignore rows removed below the currently active range
- * </ul>
+ * 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 <code>firstIndex</code>
+ * @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 */
- }
+ public Collection<GridValueChangeListener> getValueChangeListeners() {
+ return new HashSet<GridValueChangeListener>(activeItemMap.values());
}
- /**
- * 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;
- }
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ rowData.put(GridState.JSONKEY_ROWKEY, keyMapper.key(itemId));
}
- 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);
- }
}
/**
@@ -601,7 +227,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.
@@ -609,9 +236,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
@@ -620,34 +246,11 @@ public class RpcDataProviderExtension extends AbstractExtension {
private final Map<Object, Component> visibleDetailsComponents = Maps
.newHashMap();
- /** A lookup map for which row contains which details component. */
- private BiMap<Integer, Component> 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<Integer, Component> 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.
- * <p>
- * 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<Component> unattachedComponents = Sets.newHashSet();
-
/**
* Keeps tabs on all the details that did not get a component during
- * {@link #createDetails(Object, int)}.
+ * {@link #createDetails(Object)}.
*/
- private final Map<Object, Integer> emptyDetails = Maps.newHashMap();
+ private final Set<Object> emptyDetails = Sets.newHashSet();
private Grid grid;
@@ -661,19 +264,16 @@ public class RpcDataProviderExtension extends AbstractExtension {
* the item id for which to create the details component.
* Assumed not <code>null</code> 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 +284,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 +324,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
return;
}
- rowIndexToDetails.inverse().remove(removedComponent);
-
removedComponent.setParent(null);
grid.markAsDirty();
}
@@ -773,81 +339,12 @@ public class RpcDataProviderExtension extends AbstractExtension {
public Collection<Component> getComponents() {
Set<Component> components = new HashSet<Component>(
visibleDetailsComponents.values());
- components.removeAll(unattachedComponents);
return components;
}
- /**
- * Gets information on how the connectors have changed.
- * <p>
- * 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.
- * <p>
- * Used internally by the Grid object.
- *
- * @return information on how the connectors have changed
- */
- public Set<DetailsConnectorChange> getAndResetConnectorChanges() {
- Set<DetailsConnectorChange> changes = new HashSet<DetailsConnectorChange>();
-
- // populate diff with added/changed
- for (Entry<Integer, Component> 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<Integer, Component> 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) {
@@ -856,12 +353,29 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
this.grid = grid;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 7.6
+ */
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ if (visibleDetails.contains(itemId)) {
+ // Double check to be sure details component exists.
+ detailComponentManager.createDetails(itemId);
+ Component detailsComponent = visibleDetailsComponents
+ .get(itemId);
+ rowData.put(
+ GridState.JSONKEY_DETAILS_VISIBLE,
+ (detailsComponent != null ? detailsComponent
+ .getConnectorId() : ""));
+ }
+ }
}
private final Indexed container;
- private final ActiveRowHandler activeRowHandler = new ActiveRowHandler();
-
private DataProviderRpc rpc;
private final ItemSetChangeListener itemListener = new ItemSetChangeListener() {
@@ -922,22 +436,10 @@ public class RpcDataProviderExtension extends AbstractExtension {
* taking all the corner cases into account.
*/
- Map<Integer, GridValueChangeListener> listeners = activeRowHandler.valueChangeListeners;
- for (GridValueChangeListener listener : listeners.values()) {
- listener.removeListener();
- }
-
- // Wipe clean all details.
- HashSet<Object> detailItemIds = new HashSet<Object>(
- detailComponentManager.visibleDetailsComponents
- .keySet());
- for (Object itemId : detailItemIds) {
+ for (Object itemId : visibleDetails) {
detailComponentManager.destroyDetails(itemId);
}
- listeners.clear();
- activeRowHandler.activeRange = Range.withLength(0, 0);
-
/* Mark as dirty to push changes in beforeClientResponse */
bareItemSetTriggeredSizeChange = true;
markAsDirty();
@@ -945,16 +447,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>();
@@ -971,10 +466,15 @@ public class RpcDataProviderExtension extends AbstractExtension {
* This map represents all the details that are user-defined as visible.
* This does not reflect the status in the DOM.
*/
- private Set<Object> visibleDetails = new HashSet<Object>();
+ // TODO this should probably be inside DetailComponentManager
+ private final Set<Object> visibleDetails = new HashSet<Object>();
private final DetailComponentManager detailComponentManager = new DetailComponentManager();
+ private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
+
+ private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();
+
/**
* Creates a new data provider using the given container.
*
@@ -989,22 +489,15 @@ public class RpcDataProviderExtension extends AbstractExtension {
@Override
public void requestRows(int firstRow, int numberOfRows,
int firstCachedRowIndex, int cacheSize) {
-
pushRowData(firstRow, numberOfRows, firstCachedRowIndex,
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);
+ public void dropRows(JsonArray rowKeys) {
+ for (int i = 0; i < rowKeys.length(); ++i) {
+ activeItemHandler.dropActiveItem(getKeyMapper().get(
+ rowKeys.getString(i)));
}
}
});
@@ -1014,6 +507,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
.addItemSetChangeListener(itemListener);
}
+ addDataGenerator(activeItemHandler);
+ addDataGenerator(detailComponentManager);
}
/**
@@ -1045,16 +540,11 @@ 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);
+ updatedItemIds.addAll(activeItemHandler.getActiveItemIds());
}
}
- for (Object itemId : updatedItemIds) {
- internalUpdateRowData(itemId);
- }
+ internalUpdateRows(updatedItemIds);
// Clear all changes.
rowChanges.clear();
@@ -1076,7 +566,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
List<?> itemIds = container.getItemIds(fullRange.getStart(),
fullRange.length());
- keyMapper.setActiveRows(itemIds);
JsonArray rows = Json.createArray();
@@ -1088,83 +577,25 @@ 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);
}
- private JsonValue getRowData(Collection<Column> columns, Object itemId) {
+ private JsonObject getRowData(Collection<Column> columns, Object itemId) {
Item item = container.getItem(itemId);
- JsonObject rowData = Json.createObject();
-
- Grid grid = getGrid();
-
- for (Column column : columns) {
- Object propertyId = column.getPropertyId();
-
- Object propertyValue = item.getItemProperty(propertyId).getValue();
- JsonValue encodedValue = encodeValue(propertyValue,
- column.getRenderer(), column.getConverter(),
- grid.getLocale());
-
- rowData.put(columnKeys.key(propertyId), encodedValue);
- }
-
final JsonObject rowObject = Json.createObject();
- rowObject.put(GridState.JSONKEY_DATA, rowData);
- rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId));
-
- if (visibleDetails.contains(itemId)) {
- 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<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.
*
@@ -1173,13 +604,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 7.6
+ * @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 7.6
+ * @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.
*
@@ -1205,8 +661,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
rpc.insertRowData(index, count);
}
});
-
- activeRowHandler.insertRows(index, count);
}
/**
@@ -1231,8 +685,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
rpc.removeRowData(index, count);
}
});
-
- activeRowHandler.removeRows(index, count);
}
/**
@@ -1252,18 +704,20 @@ public class RpcDataProviderExtension extends AbstractExtension {
updatedItemIds.add(itemId);
}
- private void internalUpdateRowData(Object itemId) {
- int index = container.indexOfId(itemId);
- if (index >= 0) {
- JsonValue row = getRowData(getGrid().getColumns(), itemId);
- JsonArray rowArray = Json.createArray();
- rowArray.set(0, row);
- rpc.setRowData(index, rowArray);
+ private void internalUpdateRows(Set<Object> itemIds) {
+ if (itemIds.isEmpty()) {
+ return;
+ }
- if (isDetailsVisible(itemId)) {
- detailComponentManager.createDetails(itemId, index);
+ 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);
}
/**
@@ -1280,20 +734,15 @@ 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.internalDropActiveItems(activeItemHandler
+ .getActiveItemIds());
if (container instanceof ItemSetChangeNotifier) {
((ItemSetChangeNotifier) container)
.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");
}
@@ -1308,7 +757,13 @@ public class RpcDataProviderExtension extends AbstractExtension {
* a list of removed columns
*/
public void columnsRemoved(List<Column> 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.
}
/**
@@ -1318,11 +773,17 @@ public class RpcDataProviderExtension extends AbstractExtension {
* a list of added columns
*/
public void columnsAdded(List<Column> addedColumns) {
- activeRowHandler.columnsAdded(addedColumns);
+ for (GridValueChangeListener l : activeItemHandler
+ .getValueChangeListeners()) {
+ l.addColumns(addedColumns);
+ }
+
+ // Resend all rows to contain new data.
+ refreshCache();
}
- public DataProviderKeyMapper getKeyMapper() {
- return keyMapper;
+ public KeyMapper<Object> getKeyMapper() {
+ return activeItemHandler.keyMapper;
}
protected Grid getGrid() {
@@ -1330,62 +791,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 = 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.
* <p>
* If that row is currently in the client side's cache, this information
@@ -1399,37 +804,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 +843,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 +856,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/data/util/ContainerOrderedWrapper.java b/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java
index 4bb4e4c1b2..4329219e96 100644
--- a/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java
+++ b/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java
@@ -51,12 +51,14 @@ public class ContainerOrderedWrapper implements Container.Ordered,
private final Container container;
/**
- * Ordering information, ie. the mapping from Item ID to the next item ID
+ * Ordering information, ie. the mapping from Item ID to the next item ID.
+ * The last item id should not be present
*/
private Hashtable<Object, Object> next;
/**
- * Reverse ordering information for convenience and performance reasons.
+ * Reverse ordering information for convenience and performance reasons. The
+ * first item id should not be present
*/
private Hashtable<Object, Object> prev;
@@ -124,13 +126,21 @@ public class ContainerOrderedWrapper implements Container.Ordered,
first = nid;
}
if (last.equals(id)) {
- first = pid;
+ last = pid;
}
if (nid != null) {
- prev.put(nid, pid);
+ if (pid == null) {
+ prev.remove(nid);
+ } else {
+ prev.put(nid, pid);
+ }
}
if (pid != null) {
- next.put(pid, nid);
+ if (nid == null) {
+ next.remove(pid);
+ } else {
+ next.put(pid, nid);
+ }
}
next.remove(id);
prev.remove(id);
@@ -200,7 +210,7 @@ public class ContainerOrderedWrapper implements Container.Ordered,
final Collection<?> ids = container.getItemIds();
// Recreates ordering if some parts of it are missing
- if (next == null || first == null || last == null || prev != null) {
+ if (next == null || first == null || last == null || prev == null) {
first = null;
last = null;
next = new Hashtable<Object, Object>();
@@ -219,7 +229,7 @@ public class ContainerOrderedWrapper implements Container.Ordered,
// Adds missing items
for (final Iterator<?> i = ids.iterator(); i.hasNext();) {
final Object id = i.next();
- if (!next.containsKey(id)) {
+ if (!next.containsKey(id) && last != id) {
addToOrderWrapper(id);
}
}
diff --git a/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java b/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java
index 0e802da879..f965cfcc6a 100644
--- a/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java
+++ b/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java
@@ -19,20 +19,43 @@ package com.vaadin.data.util.converter;
import java.util.Locale;
/**
- * A converter that converts from {@link String} to {@link Boolean} and back.
- * The String representation is given by Boolean.toString().
- * <p>
- * Leading and trailing white spaces are ignored when converting from a String.
- * </p>
- *
+ * A converter that converts from {@link String} to {@link Boolean} and back. The String representation is given by
+ * {@link Boolean#toString()} or provided in constructor {@link #StringToBooleanConverter(String, String)}.
+ * <p> Leading and trailing white spaces are ignored when converting from a String. </p>
+ * <p> For language-dependent representation, subclasses should overwrite {@link #getFalseString(Locale)} and {@link #getTrueString(Locale)}</p>
+ *
* @author Vaadin Ltd
* @since 7.0
*/
public class StringToBooleanConverter implements Converter<String, Boolean> {
+ private final String trueString;
+
+ private final String falseString;
+
+ /**
+ * Creates converter with default string representations - "true" and "false"
+ *
+ */
+ public StringToBooleanConverter() {
+ this(Boolean.TRUE.toString(), Boolean.FALSE.toString());
+ }
+
+ /**
+ * Creates converter with custom string representation.
+ *
+ * @since 7.5.4
+ * @param falseString string representation for <code>false</code>
+ * @param trueString string representation for <code>true</code>
+ */
+ public StringToBooleanConverter(String trueString, String falseString) {
+ this.trueString = trueString;
+ this.falseString = falseString;
+ }
+
/*
* (non-Javadoc)
- *
+ *
* @see
* com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object,
* java.lang.Class, java.util.Locale)
@@ -59,26 +82,26 @@ public class StringToBooleanConverter implements Converter<String, Boolean> {
}
/**
- * Gets the string representation for true. Default is "true".
- *
+ * Gets the string representation for true. Default is "true", if not set in constructor.
+ *
* @return the string representation for true
*/
protected String getTrueString() {
- return Boolean.TRUE.toString();
+ return trueString;
}
/**
- * Gets the string representation for false. Default is "false".
- *
+ * Gets the string representation for false. Default is "false", if not set in constructor.
+ *
* @return the string representation for false
*/
protected String getFalseString() {
- return Boolean.FALSE.toString();
+ return falseString;
}
/*
* (non-Javadoc)
- *
+ *
* @see
* com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang
* .Object, java.lang.Class, java.util.Locale)
@@ -91,15 +114,39 @@ public class StringToBooleanConverter implements Converter<String, Boolean> {
return null;
}
if (value) {
- return getTrueString();
+ return getTrueString(locale);
} else {
- return getFalseString();
+ return getFalseString(locale);
}
}
+ /**
+ * Gets the locale-depended string representation for false.
+ * Default is locale-independent value provided by {@link #getFalseString()}
+ *
+ * @since 7.5.4
+ * @param locale to be used
+ * @return the string representation for false
+ */
+ protected String getFalseString(Locale locale) {
+ return getFalseString();
+ }
+
+ /**
+ * Gets the locale-depended string representation for true.
+ * Default is locale-independent value provided by {@link #getTrueString()}
+ *
+ * @since 7.5.4
+ * @param locale to be used
+ * @return the string representation for true
+ */
+ protected String getTrueString(Locale locale) {
+ return getTrueString();
+ }
+
/*
* (non-Javadoc)
- *
+ *
* @see com.vaadin.data.util.converter.Converter#getModelType()
*/
@Override
@@ -109,7 +156,7 @@ public class StringToBooleanConverter implements Converter<String, Boolean> {
/*
* (non-Javadoc)
- *
+ *
* @see com.vaadin.data.util.converter.Converter#getPresentationType()
*/
@Override
diff --git a/server/src/com/vaadin/event/ShortcutAction.java b/server/src/com/vaadin/event/ShortcutAction.java
index 09accae1c7..dd511c23c0 100644
--- a/server/src/com/vaadin/event/ShortcutAction.java
+++ b/server/src/com/vaadin/event/ShortcutAction.java
@@ -17,6 +17,7 @@
package com.vaadin.event;
import java.io.Serializable;
+import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -55,7 +56,7 @@ public class ShortcutAction extends Action {
private final int keyCode;
- private final int[] modifiers;
+ private int[] modifiers;
/**
* Creates a shortcut that reacts to the given {@link KeyCode} and
@@ -73,7 +74,7 @@ public class ShortcutAction extends Action {
public ShortcutAction(String caption, int kc, int... m) {
super(caption);
keyCode = kc;
- modifiers = m;
+ setModifiers(m);
}
/**
@@ -94,7 +95,7 @@ public class ShortcutAction extends Action {
public ShortcutAction(String caption, Resource icon, int kc, int... m) {
super(caption, icon);
keyCode = kc;
- modifiers = m;
+ setModifiers(m);
}
/**
@@ -190,7 +191,7 @@ public class ShortcutAction extends Action {
// Given modifiers override this indicated in the caption
if (modifierKeys != null) {
- modifiers = modifierKeys;
+ setModifiers(modifierKeys);
} else {
// Read modifiers from caption
int[] mod = new int[match.length() - 1];
@@ -208,13 +209,30 @@ public class ShortcutAction extends Action {
break;
}
}
- modifiers = mod;
+ setModifiers(mod);
}
} else {
keyCode = -1;
- modifiers = modifierKeys;
+ setModifiers(modifierKeys);
}
+
+ }
+
+ /**
+ * When setting modifiers, make sure that modifiers is a valid array AND
+ * that it's sorted.
+ *
+ * @param modifiers
+ * the modifier keys for this shortcut
+ */
+ private void setModifiers(int... modifiers) {
+ if (modifiers == null) {
+ this.modifiers = new int[0];
+ } else {
+ this.modifiers = modifiers;
+ }
+ Arrays.sort(this.modifiers);
}
/**
diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java
index 5a0d852299..77a1a3134e 100644
--- a/server/src/com/vaadin/server/Constants.java
+++ b/server/src/com/vaadin/server/Constants.java
@@ -137,6 +137,7 @@ public interface Constants {
static final String SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING = "legacyPropertyToString";
static final String SERVLET_PARAMETER_SYNC_ID_CHECK = "syncIdCheck";
static final String SERVLET_PARAMETER_SENDURLSASPARAMETERS = "sendUrlsAsParameters";
+ static final String SERVLET_PARAMETER_PUSH_SUSPEND_TIMEOUT_LONGPOLLING = "pushLongPollingSuspendTimeout";
// Configurable parameter names
static final String PARAMETER_VAADIN_RESOURCES = "Resources";
diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java
index 7aada2402d..61df02feaa 100644
--- a/server/src/com/vaadin/server/VaadinServlet.java
+++ b/server/src/com/vaadin/server/VaadinServlet.java
@@ -28,6 +28,7 @@ import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -694,17 +695,20 @@ public class VaadinServlet extends HttpServlet implements Constants {
return false;
}
+ String decodedRequestURI = URLDecoder.decode(request.getRequestURI(),
+ "UTF-8");
if ((request.getContextPath() != null)
- && (request.getRequestURI().startsWith("/VAADIN/"))) {
- serveStaticResourcesInVAADIN(request.getRequestURI(), request,
- response);
+ && (decodedRequestURI.startsWith("/VAADIN/"))) {
+ serveStaticResourcesInVAADIN(decodedRequestURI, request, response);
return true;
- } else if (request.getRequestURI().startsWith(
- request.getContextPath() + "/VAADIN/")) {
+ }
+
+ String decodedContextPath = URLDecoder.decode(request.getContextPath(),
+ "UTF-8");
+ if (decodedRequestURI.startsWith(decodedContextPath + "/VAADIN/")) {
serveStaticResourcesInVAADIN(
- request.getRequestURI().substring(
- request.getContextPath().length()), request,
- response);
+ decodedRequestURI.substring(decodedContextPath.length()),
+ request, response);
return true;
}
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
@@ -126,6 +126,20 @@ public class WebBrowser implements Serializable {
}
/**
+ * 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.
*
* @return true if the user is using Safari, false if the user is not using
diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
index 87ce9ba81a..5c0d2e14d4 100644
--- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
+++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
@@ -26,7 +26,9 @@ import java.io.Writer;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
+import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.atmosphere.cpr.AtmosphereResource;
@@ -274,6 +276,13 @@ public class AtmospherePushConnection implements PushConnection {
public void disconnect() {
assert isConnected();
+ if (resource == null) {
+ // Already disconnected. Should not happen but if it does, we don't
+ // want to cause NPEs
+ getLogger()
+ .fine("AtmospherePushConnection.disconnect() called twice, this should not happen");
+ return;
+ }
if (resource.isResumed()) {
// This can happen for long polling because of
// http://dev.vaadin.com/ticket/16919
@@ -345,4 +354,32 @@ public class AtmospherePushConnection implements PushConnection {
private static Logger getLogger() {
return Logger.getLogger(AtmospherePushConnection.class.getName());
}
+
+ /**
+ * Internal method used for reconfiguring loggers to show all Atmosphere log
+ * messages in the console.
+ *
+ * @since 7.6
+ */
+ public static void enableAtmosphereDebugLogging() {
+ Level level = Level.FINEST;
+
+ Logger atmosphereLogger = Logger.getLogger("org.atmosphere");
+ if (atmosphereLogger.getLevel() == level) {
+ // Already enabled
+ return;
+ }
+
+ atmosphereLogger.setLevel(level);
+
+ // Without this logging, we will have a ClassCircularityError
+ LogRecord record = new LogRecord(Level.INFO,
+ "Enabling Atmosphere debug logging");
+ atmosphereLogger.log(record);
+
+ ConsoleHandler ch = new ConsoleHandler();
+ ch.setLevel(Level.ALL);
+ atmosphereLogger.addHandler(ch);
+ }
+
}
diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java
index 576cbd8411..532c7fe95b 100644
--- a/server/src/com/vaadin/server/communication/FileUploadHandler.java
+++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java
@@ -269,7 +269,7 @@ public class FileUploadHandler implements RequestHandler {
streamVariable = uI.getConnectorTracker().getStreamVariable(
connectorId, variableName);
String secKey = uI.getConnectorTracker().getSeckey(streamVariable);
- if (!secKey.equals(parts[3])) {
+ if (secKey == null || !secKey.equals(parts[3])) {
// TODO Should rethink error handling
return true;
}
diff --git a/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java b/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java
index f36d403dd5..6d2843a4fc 100644
--- a/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java
+++ b/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java
@@ -181,8 +181,13 @@ public class JSR356WebsocketInitializer implements ServletContextListener {
*/
protected boolean isVaadinServlet(ServletRegistration servletRegistration) {
try {
- Class<?> servletClass = Class.forName(servletRegistration
- .getClassName());
+ String servletClassName = servletRegistration.getClassName();
+ if (servletClassName.equals("com.ibm.ws.wsoc.WsocServlet")) {
+ // Websphere servlet which implements websocket endpoints,
+ // dynamically added
+ return false;
+ }
+ Class<?> servletClass = Class.forName(servletClassName);
return VaadinServlet.class.isAssignableFrom(servletClass);
} catch (Exception e) {
// This will fail in OSGi environments, assume everything is a
diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java
index 01077c3f86..994415a0b4 100644
--- a/server/src/com/vaadin/server/communication/PushHandler.java
+++ b/server/src/com/vaadin/server/communication/PushHandler.java
@@ -55,6 +55,8 @@ import elemental.json.JsonException;
*/
public class PushHandler {
+ private int longPollingSuspendTimeout = -1;
+
/**
* Callback interface used internally to process an event with the
* corresponding UI properly locked.
@@ -107,7 +109,7 @@ public class PushHandler {
return;
}
- resource.suspend();
+ suspend(resource);
AtmospherePushConnection connection = getConnectionForUI(ui);
assert (connection != null);
@@ -174,6 +176,21 @@ public class PushHandler {
}
/**
+ * Suspends the given resource
+ *
+ * @since
+ * @param resource
+ * the resource to suspend
+ */
+ protected void suspend(AtmosphereResource resource) {
+ if (resource.transport() == TRANSPORT.LONG_POLLING) {
+ resource.suspend(getLongPollingSuspendTimeout());
+ } else {
+ resource.suspend(-1);
+ }
+ }
+
+ /**
* Find the UI for the atmosphere resource, lock it and invoke the callback.
*
* @param resource
@@ -493,4 +510,26 @@ public class PushHandler {
resource.transport() == TRANSPORT.WEBSOCKET);
}
+ /**
+ * Sets the timeout used for suspend calls when using long polling.
+ *
+ * If you are using a proxy with a defined idle timeout, set the suspend
+ * timeout to a value smaller than the proxy timeout so that the server is
+ * aware of a reconnect taking place.
+ *
+ * @param suspendTimeout
+ * the timeout to use for suspended AtmosphereResources
+ */
+ public void setLongPollingSuspendTimeout(int longPollingSuspendTimeout) {
+ this.longPollingSuspendTimeout = longPollingSuspendTimeout;
+ }
+
+ /**
+ * Gets the timeout used for suspend calls when using long polling.
+ *
+ * @return the timeout to use for suspended AtmosphereResources
+ */
+ public int getLongPollingSuspendTimeout() {
+ return longPollingSuspendTimeout;
+ }
}
diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java
index c01c74e5cd..c44fcd9ef3 100644
--- a/server/src/com/vaadin/server/communication/PushRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -77,7 +77,7 @@ public class PushRequestHandler implements RequestHandler,
final ServletConfig vaadinServletConfig = service.getServlet()
.getServletConfig();
- pushHandler = new PushHandler(service);
+ pushHandler = createPushHandler(service);
atmosphere = getPreInitializedAtmosphere(vaadinServletConfig);
if (atmosphere == null) {
@@ -100,7 +100,12 @@ public class PushRequestHandler implements RequestHandler,
"Using pre-initialized Atmosphere for servlet "
+ vaadinServletConfig.getServletName());
}
-
+ pushHandler
+ .setLongPollingSuspendTimeout(atmosphere
+ .getAtmosphereConfig()
+ .getInitParameter(
+ com.vaadin.server.Constants.SERVLET_PARAMETER_PUSH_SUSPEND_TIMEOUT_LONGPOLLING,
+ -1));
for (AtmosphereHandlerWrapper handlerWrapper : atmosphere
.getAtmosphereHandlers().values()) {
AtmosphereHandler handler = handlerWrapper.atmosphereHandler;
@@ -113,6 +118,22 @@ public class PushRequestHandler implements RequestHandler,
}
}
+ /**
+ * Creates a push handler for this request handler.
+ * <p>
+ * Create your own request handler and override this method if you want to
+ * customize the {@link PushHandler}, e.g. to dynamically decide the suspend
+ * timeout.
+ *
+ * @since
+ * @param service
+ * the vaadin service
+ * @return the push handler to use for this service
+ */
+ protected PushHandler createPushHandler(VaadinServletService service) {
+ return new PushHandler(service);
+ }
+
private static final Logger getLogger() {
return Logger.getLogger(PushRequestHandler.class.getName());
}
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 e9469c5bca..215df3a6a3 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,14 +46,17 @@ 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;
-import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper;
import com.vaadin.data.RpcDataProviderExtension.DetailComponentManager;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory;
@@ -77,6 +81,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;
@@ -89,13 +94,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;
@@ -106,7 +114,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;
@@ -172,7 +179,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 +518,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
*
*/
@@ -611,8 +712,9 @@ public class Grid extends AbstractComponent 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.
*
@@ -631,6 +733,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
/**
* Injects the current {@link Grid} instance into the SelectionModel.
+ * This method should usually call the extend method of
+ * {@link AbstractExtension}.
* <p>
* <em>Note:</em> This method should not be called manually.
*
@@ -872,10 +976,9 @@ public class Grid extends AbstractComponent 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<Object> selection = new LinkedHashSet<Object>();
- protected Grid grid = null;
@Override
public boolean isSelected(final Object itemId) {
@@ -889,7 +992,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
@Override
public void setGrid(final Grid grid) {
- this.grid = grid;
+ if (grid != null) {
+ extend(grid);
+ }
}
/**
@@ -903,7 +1008,7 @@ public class Grid extends AbstractComponent 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");
}
@@ -945,7 +1050,19 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
protected void fireSelectionEvent(
final Collection<Object> oldSelection,
final Collection<Object> 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;
}
}
@@ -954,8 +1071,25 @@ public class Grid extends AbstractComponent 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());
}
@@ -967,7 +1101,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
if (modified) {
final Collection<Object> deselected;
if (selectedRow != null) {
- deselectInternal(selectedRow, false);
+ deselectInternal(selectedRow, false, true);
deselected = Collections.singleton(selectedRow);
} else {
deselected = Collections.emptySet();
@@ -976,19 +1110,28 @@ public class Grid extends AbstractComponent 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;
}
@@ -1014,23 +1157,25 @@ public class Grid extends AbstractComponent 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) {
@@ -1069,6 +1214,41 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
private int selectionLimit = DEFAULT_MAX_SELECTIONS;
@Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+ registerRpc(new MultiSelectionModelServerRpc() {
+
+ @Override
+ public void select(List<String> rowKeys) {
+ List<Object> items = new ArrayList<Object>();
+ for (String rowKey : rowKeys) {
+ items.add(getItemId(rowKey));
+ }
+ MultiSelectionModel.this.select(items, false);
+ }
+
+ @Override
+ public void deselect(List<String> rowKeys) {
+ List<Object> items = new ArrayList<Object>();
+ 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)
throws IllegalArgumentException {
if (itemIds != null) {
@@ -1089,6 +1269,10 @@ public class Grid extends AbstractComponent 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");
}
@@ -1113,6 +1297,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
fireSelectionEvent(oldSelection, selection);
}
+
+ updateAllSelectedState();
+
+ if (refresh) {
+ for (Object itemId : itemIds) {
+ refreshRow(itemId);
+ }
+ }
+
return selectionWillChange;
}
@@ -1166,6 +1359,10 @@ public class Grid extends AbstractComponent 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");
}
@@ -1178,15 +1375,28 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
selection.removeAll(itemIds);
fireSelectionEvent(oldSelection, selection);
}
+
+ 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 {
@@ -1195,14 +1405,18 @@ public class Grid extends AbstractComponent 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);
}
/**
@@ -1258,6 +1472,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
fireSelectionEvent(oldSelection, selection);
}
+ updateAllSelectedState();
+
return changed;
}
@@ -1271,6 +1487,17 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
"Vararg array of itemIds may not be null");
}
}
+
+ private void updateAllSelectedState() {
+ if (getState().allSelected != selection.size() >= selectionLimit) {
+ getState().allSelected = selection.size() >= selectionLimit;
+ }
+ }
+
+ @Override
+ protected MultiSelectionModelState getState() {
+ return (MultiSelectionModelState) super.getState();
+ }
}
/**
@@ -1415,39 +1642,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 7.6
+ */
+ 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 7.6
+ */
+ 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()));
+ }
}
/**
@@ -3281,7 +3643,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 <T>
* the type this renderer knows how to present
*/
@@ -3354,7 +3716,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
* is desired. For instance, a {@code Renderer<Date>} 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
@@ -3365,11 +3727,79 @@ 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
*/
public static abstract class AbstractGridExtension extends
@@ -3384,7 +3814,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
/**
* Constructs a new Grid extension and extends given Grid.
- *
+ *
* @param grid
* a grid instance
*/
@@ -3393,6 +3823,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>
@@ -3405,7 +3855,7 @@ public class Grid extends AbstractComponent 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);
}
/**
@@ -3434,11 +3884,27 @@ 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");
}
}
+
+ /**
+ * 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);
+ }
}
/**
@@ -3514,6 +3980,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
};
+ private final ItemSetChangeListener editorClosingItemSetListener = new ItemSetChangeListener() {
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ cancelEditor();
+ }
+ };
+
private RpcDataProviderExtension datasourceExtension;
/**
@@ -3539,6 +4012,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
@@ -3628,117 +4104,10 @@ public class Grid extends AbstractComponent 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<String> selection) {
- Collection<Object> 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<Object> 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) {
assert columnIds.length == directions.length;
@@ -3767,16 +4136,9 @@ public class Grid extends AbstractComponent 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) {
- 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,
@@ -3858,10 +4220,21 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
}
@Override
- public void sendDetailsComponents(int fetchId) {
- getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges(
- detailComponentManager.getAndResetConnectorChanges(),
- fetchId);
+ public void editorOpen(String rowKey) {
+ fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper().get(
+ rowKey)));
+ }
+
+ @Override
+ public void editorMove(String rowKey) {
+ fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper().get(
+ rowKey)));
+ }
+
+ @Override
+ public void editorClose(String rowKey) {
+ fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper().get(
+ rowKey)));
}
});
@@ -3869,28 +4242,37 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
@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
@@ -3999,10 +4381,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
removeExtension(datasourceExtension);
}
- datasource = container;
-
resetEditor();
+ datasource = container;
+
//
// Adjust sort order
//
@@ -4031,7 +4413,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();
@@ -4049,6 +4432,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
@@ -4643,25 +5027,11 @@ public class Grid extends AbstractComponent 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);
}
}
@@ -4928,7 +5298,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
*
* @return the key mapper being used by the data source
*/
- DataProviderKeyMapper getKeyMapper() {
+ KeyMapper<Object> getKeyMapper() {
return datasourceExtension.getKeyMapper();
}
@@ -5489,6 +5859,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 7.6
+ */
+ 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 7.6
+ */
+ 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 7.6
+ */
+ 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 7.6
+ */
+ public RowDescriptionGenerator getRowDescriptionGenerator() {
+ return rowDescriptionGenerator;
+ }
+
+ /**
* Sets the style generator that is used for generating styles for cells
*
* @param cellStyleGenerator
@@ -5497,8 +5934,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
*/
public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
this.cellStyleGenerator = cellStyleGenerator;
- getState().hasCellStyleGenerator = (cellStyleGenerator != null);
-
datasourceExtension.refreshCache();
}
@@ -5521,8 +5956,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
*/
public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) {
this.rowStyleGenerator = rowStyleGenerator;
- getState().hasRowStyleGenerator = (rowStyleGenerator != null);
-
datasourceExtension.refreshCache();
}
@@ -5732,10 +6165,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)
@@ -5744,8 +6181,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)) {
@@ -5773,6 +6210,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) {
@@ -5822,6 +6263,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();
@@ -5971,6 +6417,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(false).editorBuffered;
+ }
+
@Override
public void addItemClickListener(ItemClickListener listener) {
addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
@@ -6063,8 +6573,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
this.detailsGenerator = detailsGenerator;
datasourceExtension.refreshDetails();
- getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges(
- detailComponentManager.getAndResetConnectorChanges(), -1);
}
/**
@@ -6088,6 +6596,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
* to hide them
*/
public void setDetailsVisible(Object itemId, boolean visible) {
+ if (DetailsGenerator.NULL.equals(detailsGenerator)) {
+ return;
+ }
datasourceExtension.setDetailsVisible(itemId, visible);
}
@@ -6265,6 +6776,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
result.add("footer-visible");
result.add("editor-error-handler");
result.add("height-mode");
+
return result;
}
}
diff --git a/server/src/com/vaadin/ui/HorizontalLayout.java b/server/src/com/vaadin/ui/HorizontalLayout.java
index 54569570b9..616fa09225 100644
--- a/server/src/com/vaadin/ui/HorizontalLayout.java
+++ b/server/src/com/vaadin/ui/HorizontalLayout.java
@@ -15,6 +15,8 @@
*/
package com.vaadin.ui;
+import com.vaadin.shared.ui.orderedlayout.HorizontalLayoutState;
+
/**
* Horizontal layout
*
@@ -48,4 +50,9 @@ public class HorizontalLayout extends AbstractOrderedLayout {
addComponents(children);
}
+ @Override
+ protected HorizontalLayoutState getState() {
+ return (HorizontalLayoutState) super.getState();
+ }
+
}
diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java
index 42c4beab6c..10d1c45ab6 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;
/**
* <p>
@@ -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,20 @@ public class Table extends AbstractSelect implements Action.Container,
if (collapsed && noncollapsibleColumns.contains(propertyId)) {
throw new IllegalStateException("The column is noncollapsible!");
}
+ if (!getContainerPropertyIds().contains(propertyId)
+ && !columnGenerators.containsKey(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 +3194,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) {
/*
@@ -5742,6 +5758,53 @@ public class Table extends AbstractSelect implements Action.Container,
}
/**
+ * 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 column
+ */
+ 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.
*
@@ -5783,6 +5846,29 @@ public class Table extends AbstractSelect implements Action.Container,
}
/**
+ * 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/server/src/com/vaadin/ui/VerticalLayout.java b/server/src/com/vaadin/ui/VerticalLayout.java
index 12819e50bc..7002fbc7e6 100644
--- a/server/src/com/vaadin/ui/VerticalLayout.java
+++ b/server/src/com/vaadin/ui/VerticalLayout.java
@@ -15,6 +15,8 @@
*/
package com.vaadin.ui;
+import com.vaadin.shared.ui.orderedlayout.VerticalLayoutState;
+
/**
* Vertical layout
*
@@ -48,4 +50,9 @@ public class VerticalLayout extends AbstractOrderedLayout {
this();
addComponents(children);
}
+
+ @Override
+ protected VerticalLayoutState getState() {
+ return (VerticalLayoutState) super.getState();
+ }
}
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
index 61ba5826b8..fd5565f900 100644
--- a/server/src/com/vaadin/ui/Window.java
+++ b/server/src/com/vaadin/ui/Window.java
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -74,7 +75,7 @@ import com.vaadin.util.ReflectTools;
* @author Vaadin Ltd.
* @since 3.0
*/
-@SuppressWarnings("serial")
+@SuppressWarnings({ "serial", "deprecation" })
public class Window extends Panel implements FocusNotifier, BlurNotifier,
LegacyComponent {
@@ -102,6 +103,11 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
};
/**
+ * Holds registered CloseShortcut instances for query and later removal
+ */
+ private List<CloseShortcut> closeShortcuts = new ArrayList<CloseShortcut>(4);
+
+ /**
* Creates a new, empty window
*/
public Window() {
@@ -130,6 +136,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
super(caption, content);
registerRpc(rpc);
setSizeUndefined();
+ setCloseShortcut(KeyCode.ESCAPE);
}
/* ********************************************************************* */
@@ -828,14 +835,22 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
}
}
- /*
- * Actions
- */
- protected CloseShortcut closeShortcut;
-
/**
- * Makes is possible to close the window by pressing the given
- * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/>
+ * This is the old way of adding a keyboard shortcut to close a
+ * {@link Window} - to preserve compatibility with existing code under the
+ * new functionality, this method now first removes all registered close
+ * shortcuts, then adds the default ESCAPE shortcut key, and then attempts
+ * to add the shortcut provided as parameters to this method. This method,
+ * and its companion {@link #removeCloseShortcut()}, are now considered
+ * deprecated, as their main function is to preserve exact backwards
+ * compatibility with old code. For all new code, use the new keyboard
+ * shortcuts API: {@link #addCloseShortcut(int,int...)},
+ * {@link #removeCloseShortcut(int,int...)},
+ * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)}
+ * and {@link #getCloseShortcuts()}.
+ * <p>
+ * Original description: Makes it possible to close the window by pressing
+ * the given {@link KeyCode} and (optional) {@link ModifierKey}s.<br/>
* Note that this shortcut only reacts while the window has focus, closing
* itself - if you want to close a window from a UI, use
* {@link UI#addAction(com.vaadin.event.Action)} of the UI instead.
@@ -843,29 +858,137 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
* @param keyCode
* the keycode for invoking the shortcut
* @param modifiers
- * the (optional) modifiers for invoking the shortcut, null for
- * none
+ * the (optional) modifiers for invoking the shortcut. Can be set
+ * to null to be explicit about not having modifiers.
+ *
+ * @deprecated Use {@link #addCloseShortcut(int, int...)} instead.
*/
+ @Deprecated
public void setCloseShortcut(int keyCode, int... modifiers) {
- if (closeShortcut != null) {
- removeAction(closeShortcut);
- }
- closeShortcut = new CloseShortcut(this, keyCode, modifiers);
- addAction(closeShortcut);
+ removeCloseShortcut();
+ addCloseShortcut(keyCode, modifiers);
}
/**
- * Removes the keyboard shortcut previously set with
- * {@link #setCloseShortcut(int, int...)}.
+ * Removes all keyboard shortcuts previously set with
+ * {@link #setCloseShortcut(int, int...)} and
+ * {@link #addCloseShortcut(int, int...)}, then adds the default
+ * {@link KeyCode#ESCAPE} shortcut.
+ * <p>
+ * This is the old way of removing the (single) keyboard close shortcut, and
+ * is retained only for exact backwards compatibility. For all new code, use
+ * the new keyboard shortcuts API: {@link #addCloseShortcut(int,int...)},
+ * {@link #removeCloseShortcut(int,int...)},
+ * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)}
+ * and {@link #getCloseShortcuts()}.
+ *
+ * @deprecated Use {@link #removeCloseShortcut(int, int...)} instead.
*/
+ @Deprecated
public void removeCloseShortcut() {
- if (closeShortcut != null) {
- removeAction(closeShortcut);
- closeShortcut = null;
+ for (int i = 0; i < closeShortcuts.size(); ++i) {
+ CloseShortcut sc = closeShortcuts.get(i);
+ removeAction(sc);
+ }
+ closeShortcuts.clear();
+ addCloseShortcut(KeyCode.ESCAPE);
+ }
+
+ /**
+ * Adds a close shortcut - pressing this key while holding down all (if any)
+ * modifiers specified while this Window is in focus will close the Window.
+ *
+ * @since
+ * @param keyCode
+ * the keycode for invoking the shortcut
+ * @param modifiers
+ * the (optional) modifiers for invoking the shortcut. Can be set
+ * to null to be explicit about not having modifiers.
+ */
+ public void addCloseShortcut(int keyCode, int... modifiers) {
+
+ // Ignore attempts to re-add existing shortcuts
+ if (hasCloseShortcut(keyCode, modifiers)) {
+ return;
+ }
+
+ // Actually add the shortcut
+ CloseShortcut shortcut = new CloseShortcut(this, keyCode, modifiers);
+ addAction(shortcut);
+ closeShortcuts.add(shortcut);
+ }
+
+ /**
+ * Removes a close shortcut previously added with
+ * {@link #addCloseShortcut(int, int...)}.
+ *
+ * @since
+ * @param keyCode
+ * the keycode for invoking the shortcut
+ * @param modifiers
+ * the (optional) modifiers for invoking the shortcut. Can be set
+ * to null to be explicit about not having modifiers.
+ */
+ public void removeCloseShortcut(int keyCode, int... modifiers) {
+ for (CloseShortcut shortcut : closeShortcuts) {
+ if (shortcut.equals(keyCode, modifiers)) {
+ removeAction(shortcut);
+ closeShortcuts.remove(shortcut);
+ return;
+ }
}
}
/**
+ * Removes all close shortcuts. This includes the default ESCAPE shortcut.
+ * It is up to the user to add back any and all keyboard close shortcuts
+ * they may require. For more fine-grained control over shortcuts, use
+ * {@link #removeCloseShortcut(int, int...)}.
+ *
+ * @since
+ */
+ public void removeAllCloseShortcuts() {
+ for (CloseShortcut shortcut : closeShortcuts) {
+ removeAction(shortcut);
+ }
+ closeShortcuts.clear();
+ }
+
+ /**
+ * Checks if a close window shortcut key has already been registered.
+ *
+ * @since
+ * @param keyCode
+ * the keycode for invoking the shortcut
+ * @param modifiers
+ * the (optional) modifiers for invoking the shortcut. Can be set
+ * to null to be explicit about not having modifiers.
+ * @return true, if an exactly matching shortcut has been registered.
+ */
+ public boolean hasCloseShortcut(int keyCode, int... modifiers) {
+ for (CloseShortcut shortcut : closeShortcuts) {
+ if (shortcut.equals(keyCode, modifiers)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns an unmodifiable collection of {@link CloseShortcut} objects
+ * currently registered with this {@link Window}. This method is provided
+ * mainly so that users can implement their own serialization routines. To
+ * check if a certain combination of keys has been registered as a close
+ * shortcut, use the {@link #hasCloseShortcut(int, int...)} method instead.
+ *
+ * @since
+ * @return an unmodifiable Collection of CloseShortcut objects.
+ */
+ public Collection<CloseShortcut> getCloseShortcuts() {
+ return Collections.unmodifiableCollection(closeShortcuts);
+ }
+
+ /**
* A {@link ShortcutListener} specifically made to define a keyboard
* shortcut that closes the window.
*
@@ -930,6 +1053,25 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
public void handleAction(Object sender, Object target) {
window.close();
}
+
+ public boolean equals(int keyCode, int... modifiers) {
+ if (keyCode != getKeyCode()) {
+ return false;
+ }
+
+ if (getModifiers() != null) {
+ int[] mods = null;
+ if (modifiers != null) {
+ // Modifiers provided by the parent ShortcutAction class
+ // are guaranteed to be sorted. We still need to sort
+ // the modifiers passed in as argument.
+ mods = Arrays.copyOf(modifiers, modifiers.length);
+ Arrays.sort(mods);
+ }
+ return Arrays.equals(mods, getModifiers());
+ }
+ return true;
+ }
}
/*
@@ -1244,11 +1386,26 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
setPositionX(Integer.parseInt(position[0]));
setPositionY(Integer.parseInt(position[1]));
}
+
+ // Parse shortcuts if defined, otherwise rely on default behavior
if (design.hasAttr("close-shortcut")) {
- ShortcutAction shortcut = DesignAttributeHandler
- .readAttribute("close-shortcut", design.attributes(),
- ShortcutAction.class);
- setCloseShortcut(shortcut.getKeyCode(), shortcut.getModifiers());
+
+ // Parse shortcuts
+ String[] shortcutStrings = DesignAttributeHandler.readAttribute(
+ "close-shortcut", design.attributes(), String.class).split(
+ "\\s+");
+
+ removeAllCloseShortcuts();
+
+ for (String part : shortcutStrings) {
+ if (!part.isEmpty()) {
+ ShortcutAction shortcut = DesignAttributeHandler
+ .getFormatter().parse(part.trim(),
+ ShortcutAction.class);
+ addCloseShortcut(shortcut.getKeyCode(),
+ shortcut.getModifiers());
+ }
+ }
}
}
@@ -1302,19 +1459,24 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
DesignAttributeHandler.writeAttribute("position", design.attributes(),
getPosition(), def.getPosition(), String.class);
- CloseShortcut shortcut = getCloseShortcut();
- if (shortcut != null) {
- // TODO What if several close shortcuts??
-
- CloseShortcut defShortcut = def.getCloseShortcut();
- if (defShortcut == null
- || shortcut.getKeyCode() != defShortcut.getKeyCode()
- || !Arrays.equals(shortcut.getModifiers(),
- defShortcut.getModifiers())) {
- DesignAttributeHandler.writeAttribute("close-shortcut",
- design.attributes(), shortcut, null,
- CloseShortcut.class);
+ // Process keyboard shortcuts
+ if (closeShortcuts.size() == 1 && hasCloseShortcut(KeyCode.ESCAPE)) {
+ // By default, we won't write anything if we're relying on default
+ // shortcut behavior
+ } else {
+ // Dump all close shortcuts to a string...
+ String attrString = "";
+
+ // TODO: add canonical support for array data in XML attributes
+ for (CloseShortcut shortcut : closeShortcuts) {
+ String shortcutString = DesignAttributeHandler.getFormatter()
+ .format(shortcut, CloseShortcut.class);
+ attrString += shortcutString + " ";
}
+
+ // Write everything except the last "," to the design
+ DesignAttributeHandler.writeAttribute("close-shortcut",
+ design.attributes(), attrString.trim(), null, String.class);
}
for (Component c : getAssistiveDescription()) {
@@ -1328,10 +1490,6 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
return getPositionX() + "," + getPositionY();
}
- private CloseShortcut getCloseShortcut() {
- return closeShortcut;
- }
-
@Override
protected Collection<String> getCustomAttributes() {
Collection<String> result = super.getCustomAttributes();
diff --git a/server/src/com/vaadin/ui/renderers/ImageRenderer.java b/server/src/com/vaadin/ui/renderers/ImageRenderer.java
index 2fb872583e..ad7d5cae2b 100644
--- a/server/src/com/vaadin/ui/renderers/ImageRenderer.java
+++ b/server/src/com/vaadin/ui/renderers/ImageRenderer.java
@@ -58,7 +58,7 @@ public class ImageRenderer extends ClickableRenderer<Resource> {
if (!(resource == null || resource instanceof ExternalResource || resource instanceof ThemeResource)) {
throw new IllegalArgumentException(
"ImageRenderer only supports ExternalResource and ThemeResource ("
- + resource.getClass().getSimpleName() + "given )");
+ + resource.getClass().getSimpleName() + " given)");
}
return encode(ResourceReference.create(resource, this, null),
diff --git a/server/src/com/vaadin/ui/themes/ValoTheme.java b/server/src/com/vaadin/ui/themes/ValoTheme.java
index 1285bf7f67..3a9986c632 100644
--- a/server/src/com/vaadin/ui/themes/ValoTheme.java
+++ b/server/src/com/vaadin/ui/themes/ValoTheme.java
@@ -709,7 +709,7 @@ public class ValoTheme {
* area is scrolled. Suitable with the {@link #PANEL_BORDERLESS} style. Can
* be combined with any other Panel style.
*/
- public static final String PANEL_SCROLL_INDICATOR = "scroll-indicator";
+ public static final String PANEL_SCROLL_INDICATOR = "scroll-divider";
/**
* Inset panel style. Can be combined with any other Panel style.
diff --git a/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java b/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java
index 54cbc5305d..55dccc6455 100644
--- a/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java
+++ b/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java
@@ -11,6 +11,7 @@ import com.vaadin.data.Container;
import com.vaadin.data.Container.Filterable;
import com.vaadin.data.Container.ItemSetChangeEvent;
import com.vaadin.data.Container.ItemSetChangeListener;
+import com.vaadin.data.Container.Ordered;
import com.vaadin.data.Container.Sortable;
import com.vaadin.data.Item;
import com.vaadin.data.util.filter.SimpleStringFilter;
@@ -161,54 +162,54 @@ public abstract class AbstractContainerTestBase extends TestCase {
validateContainer(container, sampleData[0],
sampleData[sampleData.length - 1], sampleData[10], "abc", true,
sampleData.length);
+
+ validateRemovingItems(container);
+ }
+
+ protected void validateRemovingItems(Container container) {
+ int sizeBeforeRemoving = container.size();
+
+ List<Object> itemIdList = new ArrayList<Object>(container.getItemIds());
+ // There should be at least four items in the list
+ Object first = itemIdList.get(0);
+ Object middle = itemIdList.get(2);
+ Object last = itemIdList.get(itemIdList.size() - 1);
+
+ container.removeItem(first);
+ container.removeItem(middle); // Middle now that first has been removed
+ container.removeItem(last);
+
+ assertEquals(sizeBeforeRemoving - 3, container.size());
+
+ container.removeAllItems();
+
+ assertEquals(0, container.size());
}
protected void testContainerOrdered(Container.Ordered container) {
+ // addItem with empty container
Object id = container.addItem();
- assertNotNull(id);
+ assertOrderedContents(container, id);
Item item = container.getItem(id);
assertNotNull(item);
- assertEquals(id, container.firstItemId());
- assertEquals(id, container.lastItemId());
-
- // isFirstId
- assertTrue(container.isFirstId(id));
- assertTrue(container.isFirstId(container.firstItemId()));
- // isLastId
- assertTrue(container.isLastId(id));
- assertTrue(container.isLastId(container.lastItemId()));
+ // addItemAfter with empty container
+ container.removeAllItems();
+ assertOrderedContents(container);
+ id = container.addItemAfter(null);
+ assertOrderedContents(container, id);
+ item = container.getItem(id);
+ assertNotNull(item);
// Add a new item before the first
// addItemAfter
Object newFirstId = container.addItemAfter(null);
- assertNotNull(newFirstId);
- assertNotNull(container.getItem(newFirstId));
-
- // isFirstId
- assertTrue(container.isFirstId(newFirstId));
- assertTrue(container.isFirstId(container.firstItemId()));
- // isLastId
- assertTrue(container.isLastId(id));
- assertTrue(container.isLastId(container.lastItemId()));
-
- // nextItemId
- assertEquals(id, container.nextItemId(newFirstId));
- assertNull(container.nextItemId(id));
- assertNull(container.nextItemId("not-in-container"));
-
- // prevItemId
- assertEquals(newFirstId, container.prevItemId(id));
- assertNull(container.prevItemId(newFirstId));
- assertNull(container.prevItemId("not-in-container"));
+ assertOrderedContents(container, newFirstId, id);
// addItemAfter(Object)
Object newSecondItemId = container.addItemAfter(newFirstId);
// order is now: newFirstId, newSecondItemId, id
- assertNotNull(newSecondItemId);
- assertNotNull(container.getItem(newSecondItemId));
- assertEquals(id, container.nextItemId(newSecondItemId));
- assertEquals(newFirstId, container.prevItemId(newSecondItemId));
+ assertOrderedContents(container, newFirstId, newSecondItemId, id);
// addItemAfter(Object,Object)
String fourthId = "id of the fourth item";
@@ -216,8 +217,8 @@ public abstract class AbstractContainerTestBase extends TestCase {
// order is now: newFirstId, fourthId, newSecondItemId, id
assertNotNull(fourth);
assertEquals(fourth, container.getItem(fourthId));
- assertEquals(newSecondItemId, container.nextItemId(fourthId));
- assertEquals(newFirstId, container.prevItemId(fourthId));
+ assertOrderedContents(container, newFirstId, fourthId, newSecondItemId,
+ id);
// addItemAfter(Object,Object)
Object fifthId = new Object();
@@ -225,8 +226,86 @@ public abstract class AbstractContainerTestBase extends TestCase {
// order is now: fifthId, newFirstId, fourthId, newSecondItemId, id
assertNotNull(fifth);
assertEquals(fifth, container.getItem(fifthId));
- assertEquals(newFirstId, container.nextItemId(fifthId));
- assertNull(container.prevItemId(fifthId));
+ assertOrderedContents(container, fifthId, newFirstId, fourthId,
+ newSecondItemId, id);
+
+ // addItemAfter(Object,Object)
+ Object sixthId = new Object();
+ Item sixth = container.addItemAfter(id, sixthId);
+ // order is now: fifthId, newFirstId, fourthId, newSecondItemId, id,
+ // sixthId
+ assertNotNull(sixth);
+ assertEquals(sixth, container.getItem(sixthId));
+ assertOrderedContents(container, fifthId, newFirstId, fourthId,
+ newSecondItemId, id, sixthId);
+
+ // Test order after removing first item 'fifthId'
+ container.removeItem(fifthId);
+ // order is now: newFirstId, fourthId, newSecondItemId, id, sixthId
+ assertOrderedContents(container, newFirstId, fourthId, newSecondItemId,
+ id, sixthId);
+
+ // Test order after removing last item 'sixthId'
+ container.removeItem(sixthId);
+ // order is now: newFirstId, fourthId, newSecondItemId, id
+ assertOrderedContents(container, newFirstId, fourthId, newSecondItemId,
+ id);
+
+ // Test order after removing item from the middle 'fourthId'
+ container.removeItem(fourthId);
+ // order is now: newFirstId, newSecondItemId, id
+ assertOrderedContents(container, newFirstId, newSecondItemId, id);
+
+ // Delete remaining items
+ container.removeItem(newFirstId);
+ container.removeItem(newSecondItemId);
+ container.removeItem(id);
+ assertOrderedContents(container);
+
+ Object finalItem = container.addItem();
+ assertOrderedContents(container, finalItem);
+ }
+
+ private void assertOrderedContents(Ordered container, Object... ids) {
+ assertEquals(ids.length, container.size());
+ for (int i = 0; i < ids.length - 1; i++) {
+ assertNotNull("The item id should not be null", ids[i]);
+ }
+ if (ids.length == 0) {
+ assertNull("The first id is wrong", container.firstItemId());
+ assertNull("The last id is wrong", container.lastItemId());
+ return;
+ }
+
+ assertEquals("The first id is wrong", ids[0], container.firstItemId());
+ assertEquals("The last id is wrong", ids[ids.length - 1],
+ container.lastItemId());
+
+ // isFirstId & isLastId
+ assertTrue(container.isFirstId(container.firstItemId()));
+ assertTrue(container.isLastId(container.lastItemId()));
+
+ // nextId
+ Object ref = container.firstItemId();
+ for (int i = 1; i < ids.length; i++) {
+ Object next = container.nextItemId(ref);
+ assertEquals("The id after " + ref + " is wrong", ids[i], next);
+ ref = next;
+ }
+ assertNull("The last id should not have a next id",
+ container.nextItemId(ids[ids.length - 1]));
+ assertNull(container.nextItemId("not-in-container"));
+
+ // prevId
+ ref = container.lastItemId();
+ for (int i = ids.length - 2; i >= 0; i--) {
+ Object prev = container.prevItemId(ref);
+ assertEquals("The id before " + ref + " is wrong", ids[i], prev);
+ ref = prev;
+ }
+ assertNull("The first id should not have a prev id",
+ container.prevItemId(ids[0]));
+ assertNull(container.prevItemId("not-in-container"));
}
diff --git a/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java b/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java
index 3bd00cce3c..9cede77162 100644
--- a/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java
+++ b/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java
@@ -253,4 +253,33 @@ public abstract class AbstractHierarchicalContainerTestBase extends
}
}
+ protected void testRemoveHierarchicalWrapperSubtree(
+ Container.Hierarchical container) {
+ initializeContainer(container);
+
+ // remove root item
+ removeItemRecursively(container, "org");
+
+ int packages = 21 + 3 - 3;
+ int expectedSize = sampleData.length + packages - 1;
+
+ validateContainer(container, "com", "com.vaadin.util.SerializerHelper",
+ "com.vaadin.server.ApplicationResource", "blah", true,
+ expectedSize);
+
+ // rootItemIds
+ Collection<?> rootIds = container.rootItemIds();
+ assertEquals(1, rootIds.size());
+ }
+
+ private void removeItemRecursively(Container.Hierarchical container,
+ Object itemId) {
+ if (container instanceof ContainerHierarchicalWrapper) {
+ ((ContainerHierarchicalWrapper) container)
+ .removeItemRecursively("org");
+ } else {
+ HierarchicalContainer.removeItemRecursively(container, itemId);
+ }
+ }
+
}
diff --git a/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java b/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java
index 2fd21ef118..8e554d5030 100644
--- a/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java
+++ b/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java
@@ -1,6 +1,5 @@
package com.vaadin.data.util;
-import java.util.Collection;
public class ContainerHierarchicalWrapperTest extends
AbstractHierarchicalContainerTestBase {
@@ -20,23 +19,4 @@ public class ContainerHierarchicalWrapperTest extends
new IndexedContainer()));
}
- protected void testRemoveHierarchicalWrapperSubtree(
- ContainerHierarchicalWrapper container) {
- initializeContainer(container);
-
- // remove root item
- container.removeItemRecursively("org");
-
- int packages = 21 + 3 - 3;
- int expectedSize = sampleData.length + packages - 1;
-
- validateContainer(container, "com", "com.vaadin.util.SerializerHelper",
- "com.vaadin.server.ApplicationResource", "blah", true,
- expectedSize);
-
- // rootItemIds
- Collection<?> rootIds = container.rootItemIds();
- assertEquals(1, rootIds.size());
- }
-
}
diff --git a/server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java b/server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java
new file mode 100644
index 0000000000..422e9756f8
--- /dev/null
+++ b/server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java
@@ -0,0 +1,102 @@
+package com.vaadin.data.util;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+public class ContainerOrderedWrapperTest extends AbstractContainerTestBase {
+
+ // This class is needed to get an implementation of container
+ // which is not an implementation of Ordered interface.
+ private class NotOrderedContainer implements Container {
+
+ private IndexedContainer container;
+
+ public NotOrderedContainer() {
+ container = new IndexedContainer();
+ }
+
+ @Override
+ public Item getItem(Object itemId) {
+ return container.getItem(itemId);
+ }
+
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ return container.getContainerPropertyIds();
+ }
+
+ @Override
+ public Collection<?> getItemIds() {
+ return container.getItemIds();
+ }
+
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ return container.getContainerProperty(itemId, propertyId);
+ }
+
+ @Override
+ public Class<?> getType(Object propertyId) {
+ return container.getType(propertyId);
+ }
+
+ @Override
+ public int size() {
+ return container.size();
+ }
+
+ @Override
+ public boolean containsId(Object itemId) {
+ return container.containsId(itemId);
+ }
+
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ return container.addItem(itemId);
+ }
+
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+ return container.addItem();
+ }
+
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ return container.removeItem(itemId);
+ }
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ return container.addContainerProperty(propertyId, type,
+ defaultValue);
+ }
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ return container.removeContainerProperty(propertyId);
+ }
+
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ return container.removeAllItems();
+ }
+
+ }
+
+ public void testBasicOperations() {
+ testBasicContainerOperations(new ContainerOrderedWrapper(
+ new NotOrderedContainer()));
+ }
+
+ public void testOrdered() {
+ testContainerOrdered(new ContainerOrderedWrapper(
+ new NotOrderedContainer()));
+ }
+
+}
diff --git a/server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java b/server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java
new file mode 100644
index 0000000000..7ecf59c309
--- /dev/null
+++ b/server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java
@@ -0,0 +1,27 @@
+package com.vaadin.data.util;
+
+public class HierarchicalContainerOrderedWrapperTest extends
+ AbstractHierarchicalContainerTestBase {
+
+ private HierarchicalContainerOrderedWrapper createContainer() {
+ return new HierarchicalContainerOrderedWrapper(
+ new ContainerHierarchicalWrapper(new IndexedContainer()));
+ }
+
+ public void testBasicOperations() {
+ testBasicContainerOperations(createContainer());
+ }
+
+ public void testHierarchicalContainer() {
+ testHierarchicalContainer(createContainer());
+ }
+
+ public void testContainerOrdered() {
+ testContainerOrdered(createContainer());
+ }
+
+ public void testRemoveSubtree() {
+ testRemoveHierarchicalWrapperSubtree(createContainer());
+ }
+
+}
diff --git a/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java b/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java
index 2cb4c3bf4d..286163541e 100644
--- a/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java
+++ b/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java
@@ -15,50 +15,136 @@
*/
package com.vaadin.server.communication;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.StreamVariable;
import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.UI;
-/**
- * Tests whether we get infinite loop if InputStream is already read (#10096)
- */
public class FileUploadHandlerTest {
private FileUploadHandler handler;
- private VaadinRequest request;
+ @Mock private VaadinResponse response;
+ @Mock private StreamVariable streamVariable;
+ @Mock private ClientConnector clientConnector;
+ @Mock private VaadinRequest request;
+ @Mock private UI ui;
+ @Mock private ConnectorTracker connectorTracker;
+ @Mock private VaadinSession session;
+ @Mock private OutputStream responseOutput;
+
+ private int uiId = 123;
+ private final String connectorId = "connectorId";
+ private final String variableName = "name";
+ private final String expectedSecurityKey = "key";
@Before
public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
handler = new FileUploadHandler();
- InputStream inputStream = new InputStream() {
- private int counter = 0;
+
+ mockRequest();
+ mockConnectorTracker();
+ mockUi();
+
+ when(clientConnector.isConnectorEnabled()).thenReturn(true);
+ when(streamVariable.getOutputStream()).thenReturn(mock(OutputStream.class));
+ when(response.getOutputStream()).thenReturn(responseOutput);
+ }
+
+ private void mockConnectorTracker() {
+ when(connectorTracker.getSeckey(streamVariable)).thenReturn(expectedSecurityKey);
+ when(connectorTracker.getStreamVariable(connectorId, variableName)).thenReturn(streamVariable);
+ when(connectorTracker.getConnector(connectorId)).thenReturn(clientConnector);
+ }
+
+ private void mockRequest() throws IOException {
+ when(request.getPathInfo()).thenReturn("/" + ServletPortletHelper.UPLOAD_URL_PREFIX + uiId + "/"+ connectorId + "/" + variableName + "/" + expectedSecurityKey);
+ when(request.getInputStream()).thenReturn(createInputStream("foobar"));
+ when(request.getHeader("Content-Length")).thenReturn("6");
+ when(request.getContentType()).thenReturn("foobar");
+ }
+
+ private InputStream createInputStream(final String content) {
+ return new InputStream() {
+ int counter = 0;
+ byte[] msg = content.getBytes();
@Override
public int read() throws IOException {
- counter++;
- if (counter > 6) {
- throw new RuntimeException(
- "-1 is ignored by FileUploadHandler");
+ if(counter > msg.length + 1) {
+ throw new AssertionError("-1 was ignored by FileUploadHandler.");
+ }
+
+ if(counter >= msg.length) {
+ counter++;
+ return -1;
}
- return -1;
- }
+ return msg[counter++];
+ }
};
- request = Mockito.mock(VaadinRequest.class);
- Mockito.when(request.getInputStream()).thenReturn(inputStream);
- Mockito.when(request.getHeader("Content-Length")).thenReturn("211");
}
+ private void mockUi() {
+ when(ui.getConnectorTracker()).thenReturn(connectorTracker);
+ when(session.getUIById(uiId)).thenReturn(ui);
+ }
+
+ /**
+ * Tests whether we get infinite loop if InputStream is already read (#10096)
+ */
@Test(expected = IOException.class)
- public void testStreamEnded() throws IOException {
+ public void exceptionIsThrownOnUnexpectedEnd() throws IOException {
+ when(request.getInputStream()).thenReturn(createInputStream(""));
+ when(request.getHeader("Content-Length")).thenReturn("1");
+
handler.doHandleSimpleMultipartFileUpload(null, request, null, null,
null, null, null);
+ }
+
+ @Test
+ public void responseIsSentOnCorrectSecurityKey() throws IOException {
+ when(connectorTracker.getSeckey(streamVariable)).thenReturn(expectedSecurityKey);
+
+ handler.handleRequest(session, request, response);
+ verify(responseOutput).close();
}
+ @Test
+ public void responseIsNotSentOnIncorrectSecurityKey() throws IOException {
+ when(connectorTracker.getSeckey(streamVariable)).thenReturn("another key expected");
+
+ handler.handleRequest(session, request, response);
+
+ verifyZeroInteractions(responseOutput);
+ }
+
+ @Test
+ public void responseIsNotSentOnMissingSecurityKey() throws IOException {
+ when(connectorTracker.getSeckey(streamVariable)).thenReturn(null);
+
+ handler.handleRequest(session, request, response);
+
+ verifyZeroInteractions(responseOutput);
+ }
}
diff --git a/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java b/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java
index f734d76633..6e81af97a3 100644
--- a/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java
+++ b/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java
@@ -4,9 +4,25 @@ import junit.framework.TestCase;
import com.vaadin.data.util.converter.StringToBooleanConverter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
public class StringToBooleanConverterTest extends TestCase {
StringToBooleanConverter converter = new StringToBooleanConverter();
+ StringToBooleanConverter yesNoConverter = new StringToBooleanConverter("yes","no");
+ StringToBooleanConverter localeConverter = new StringToBooleanConverter() {
+ @Override
+ public String getFalseString(Locale locale) {
+ return SimpleDateFormat.getDateInstance(SimpleDateFormat.LONG,locale).format(new Date(3000000000000L));
+ }
+
+ @Override
+ public String getTrueString(Locale locale) {
+ return SimpleDateFormat.getDateInstance(SimpleDateFormat.LONG,locale).format(new Date(2000000000000L));
+ }
+ };
public void testNullConversion() {
assertEquals(null, converter.convertToModel(null, Boolean.class, null));
@@ -20,4 +36,22 @@ public class StringToBooleanConverterTest extends TestCase {
assertTrue(converter.convertToModel("true", Boolean.class, null));
assertFalse(converter.convertToModel("false", Boolean.class, null));
}
+
+ public void testYesNoValueConversion() {
+ assertTrue(yesNoConverter.convertToModel("yes", Boolean.class, null));
+ assertFalse(yesNoConverter.convertToModel("no", Boolean.class, null));
+
+ assertEquals("yes", yesNoConverter.convertToPresentation(true, String.class, null));
+ assertEquals("no", yesNoConverter.convertToPresentation(false, String.class, null));
+ }
+
+
+ public void testLocale() {
+ assertEquals("May 18, 2033", localeConverter.convertToPresentation(true, String.class, Locale.US));
+ assertEquals("January 24, 2065", localeConverter.convertToPresentation(false, String.class, Locale.US));
+
+ assertEquals("18. Mai 2033", localeConverter.convertToPresentation(true, String.class, Locale.GERMANY));
+ assertEquals("24. Januar 2065", localeConverter.convertToPresentation(false, String.class, Locale.GERMANY));
+ }
+
}
diff --git a/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java b/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java
index a4c3732e1f..59c1ed133a 100644
--- a/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java
+++ b/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java
@@ -1,13 +1,14 @@
package com.vaadin.tests.data.converter;
-import junit.framework.TestCase;
+import org.junit.Assert;
+import org.junit.Test;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.Converter.ConversionException;
import com.vaadin.data.util.converter.ReverseConverter;
import com.vaadin.data.util.converter.StringToEnumConverter;
-public class StringToEnumConverterTest extends TestCase {
+public class StringToEnumConverterTest {
public static enum FooEnum {
VALUE1, SOME_VALUE, FOO_BAR_BAZ, Bar, nonStandardCase, _HUGH;
@@ -17,56 +18,64 @@ public class StringToEnumConverterTest extends TestCase {
Converter<Enum, String> reverseConverter = new ReverseConverter<Enum, String>(
converter);
+ @Test
public void testEmptyStringConversion() {
- assertEquals(null, converter.convertToModel("", Enum.class, null));
+ Assert.assertEquals(null,
+ converter.convertToModel("", Enum.class, null));
}
+ @Test
public void testInvalidEnumClassConversion() {
try {
converter.convertToModel("Foo", Enum.class, null);
- fail("No exception thrown");
+ Assert.fail("No exception thrown");
} catch (ConversionException e) {
// OK
}
}
+ @Test
public void testNullConversion() {
- assertEquals(null, converter.convertToModel(null, Enum.class, null));
+ Assert.assertEquals(null,
+ converter.convertToModel(null, Enum.class, null));
}
+ @Test
public void testReverseNullConversion() {
- assertEquals(null,
+ Assert.assertEquals(null,
reverseConverter.convertToModel(null, String.class, null));
}
+ @Test
public void testValueConversion() {
- assertEquals(FooEnum.VALUE1,
+ Assert.assertEquals(FooEnum.VALUE1,
converter.convertToModel("Value1", FooEnum.class, null));
- assertEquals(FooEnum.SOME_VALUE,
+ Assert.assertEquals(FooEnum.SOME_VALUE,
converter.convertToModel("Some value", FooEnum.class, null));
- assertEquals(FooEnum.FOO_BAR_BAZ,
+ Assert.assertEquals(FooEnum.FOO_BAR_BAZ,
converter.convertToModel("Foo bar baz", FooEnum.class, null));
- assertEquals(FooEnum.Bar,
+ Assert.assertEquals(FooEnum.Bar,
converter.convertToModel("Bar", FooEnum.class, null));
- assertEquals(FooEnum.nonStandardCase, converter.convertToModel(
+ Assert.assertEquals(FooEnum.nonStandardCase, converter.convertToModel(
"Nonstandardcase", FooEnum.class, null));
- assertEquals(FooEnum._HUGH,
+ Assert.assertEquals(FooEnum._HUGH,
converter.convertToModel("_hugh", FooEnum.class, null));
}
+ @Test
public void testReverseValueConversion() {
- assertEquals("Value1", reverseConverter.convertToModel(FooEnum.VALUE1,
- String.class, null));
- assertEquals("Some value", reverseConverter.convertToModel(
+ Assert.assertEquals("Value1", reverseConverter.convertToModel(
+ FooEnum.VALUE1, String.class, null));
+ Assert.assertEquals("Some value", reverseConverter.convertToModel(
FooEnum.SOME_VALUE, String.class, null));
- assertEquals("Foo bar baz", reverseConverter.convertToModel(
+ Assert.assertEquals("Foo bar baz", reverseConverter.convertToModel(
FooEnum.FOO_BAR_BAZ, String.class, null));
- assertEquals("Bar", reverseConverter.convertToModel(FooEnum.Bar,
+ Assert.assertEquals("Bar", reverseConverter.convertToModel(FooEnum.Bar,
String.class, null));
- assertEquals("Nonstandardcase", reverseConverter.convertToModel(
+ Assert.assertEquals("Nonstandardcase", reverseConverter.convertToModel(
FooEnum.nonStandardCase, String.class, null));
- assertEquals("_hugh", reverseConverter.convertToModel(FooEnum._HUGH,
- String.class, null));
+ Assert.assertEquals("_hugh", reverseConverter.convertToModel(
+ FooEnum._HUGH, String.class, null));
}
diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java
index acee3e2ca8..6510d8ad40 100644
--- a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java
+++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java
@@ -203,7 +203,7 @@ public class DesignFormatterTest {
ShortcutAction action = new ShortcutAction("&^d");
String formatted = formatter.format(action);
// note the space here - it separates key combination from caption
- assertEquals("alt-ctrl-d d", formatted);
+ assertEquals("ctrl-alt-d d", formatted);
ShortcutAction result = formatter
.parse(formatted, ShortcutAction.class);
@@ -215,7 +215,7 @@ public class DesignFormatterTest {
ShortcutAction action = new ShortcutAction(null, KeyCode.D, new int[] {
ModifierKey.ALT, ModifierKey.CTRL });
String formatted = formatter.format(action);
- assertEquals("alt-ctrl-d", formatted);
+ assertEquals("ctrl-alt-d", formatted);
ShortcutAction result = formatter
.parse(formatted, ShortcutAction.class);
diff --git a/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java b/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java
index 9603032ce5..829ad0455d 100644
--- a/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java
+++ b/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java
@@ -73,7 +73,7 @@ public class ClassesSerializableTest extends TestCase {
"com\\.vaadin\\.server\\.AbstractClientConnector\\$1\\$1", //
"com\\.vaadin\\.server\\.JsonCodec\\$1", //
"com\\.vaadin\\.server\\.communication\\.PushConnection", //
- "com\\.vaadin\\.server\\.communication\\.AtmospherePushConnection", //
+ "com\\.vaadin\\.server\\.communication\\.AtmospherePushConnection.*", //
"com\\.vaadin\\.util\\.ConnectorHelper", //
"com\\.vaadin\\.server\\.VaadinSession\\$FutureAccess", //
"com\\.vaadin\\.external\\..*", //
diff --git a/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java
index 51abf6be96..b1c10789b4 100644
--- a/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java
+++ b/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java
@@ -98,7 +98,7 @@ public class ButtonDeclarativeTest extends DeclarativeTestBase<Button> {
@Test
public void testAttributes() {
String design = "<v-button tabindex=3 plain-text='' icon-alt=OK "
- + "click-shortcut=ctrl-shift-o></v-button>";
+ + "click-shortcut=shift-ctrl-o></v-button>";
Button b = new Button("");
b.setTabIndex(3);
b.setIconAlternateText("OK");
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<String> 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/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java
index 1d233af494..607fb471b9 100644
--- a/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java
+++ b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java
@@ -71,8 +71,9 @@ public class WindowDeclarativeTest extends DeclarativeTestBase<Window> {
expected.setClosable(!expected.isClosable());
expected.setDraggable(!expected.isDraggable());
- expected.setCloseShortcut(KeyCode.ESCAPE, ModifierKey.CTRL,
- ModifierKey.ALT);
+ expected.removeAllCloseShortcuts();
+ expected.addCloseShortcut(KeyCode.ESCAPE, ModifierKey.ALT,
+ ModifierKey.CTRL);
expected.setAssistivePrefix("Hello");
expected.setAssistivePostfix("World");
@@ -86,6 +87,40 @@ public class WindowDeclarativeTest extends DeclarativeTestBase<Window> {
}
@Test
+ public void testMultiCloseShortcuts() {
+
+ Window expected = new Window();
+
+ // Add two shortcuts - should now contain three (default escape + two
+ // added)
+ expected.addCloseShortcut(KeyCode.SPACEBAR);
+ expected.addCloseShortcut(KeyCode.ARROW_LEFT, ModifierKey.ALT,
+ ModifierKey.CTRL);
+
+ // Try to add the same shortcut again, should be no-op
+ expected.addCloseShortcut(KeyCode.ARROW_LEFT, ModifierKey.CTRL,
+ ModifierKey.ALT);
+
+ // Add a third shortcut, should total four (default escape + three
+ // added)
+ expected.addCloseShortcut(KeyCode.ARROW_RIGHT, ModifierKey.CTRL);
+
+ // Test validity
+ String design = "<v-window close-shortcut='escape spacebar ctrl-alt-left ctrl-right' />";
+ testRead(design, expected);
+ testWrite(design, expected);
+
+ // Try removing the spacebar shortcut
+ expected.removeCloseShortcut(KeyCode.SPACEBAR);
+
+ // Test again
+ design = "<v-window close-shortcut='escape ctrl-alt-left ctrl-right' />";
+ testRead(design, expected);
+ testWrite(design, expected);
+
+ }
+
+ @Test
public void testInvalidPosition() {
assertInvalidPosition("");
assertInvalidPosition("1");
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());
}
}