Sfoglia il codice sorgente

Merge branch 'master' into grid-unbuffered-editor

Conflicts:
	server/src/com/vaadin/data/RpcDataProviderExtension.java

Change-Id: I1bd55b03a8c114823ed8655fc89758f37b16e9c4
tags/7.6.0.alpha4
Henri Sara 8 anni fa
parent
commit
d3ccbfc53b

+ 27
- 0
client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java Vedi File

@@ -17,6 +17,7 @@
package com.vaadin.client.connectors;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.vaadin.client.ServerConnector;
@@ -96,6 +97,11 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
public void resetDataAndSize(int size) {
RpcDataSource.this.resetDataAndSize(size);
}

@Override
public void updateRowData(JsonObject row) {
RpcDataSource.this.updateRowData(row);
}
});
}

@@ -213,6 +219,27 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
rowData.get(i));
}
}

/**
* Updates row data based on row key.
*
* @since
* @param row
* new row object
*/
protected void updateRowData(JsonObject row) {
int index = indexOfKey(getRowKey(row));
if (index >= 0) {
setRowData(index, Collections.singletonList(row));
}
}

@Override
protected void onDropFromCache(int rowIndex) {
super.onDropFromCache(rowIndex);

rpcProxy.dropRow(getRowKey(getRow(rowIndex)));
}
}

private final RpcDataSource dataSource = new RpcDataSource();

+ 14
- 4
client/src/com/vaadin/client/data/AbstractRemoteDataSource.java Vedi File

@@ -330,16 +330,18 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {

private void dropFromCache(Range range) {
for (int i = range.getStart(); i < range.getEnd(); i++) {
// Called before dropping from cache, so we can actually do
// something with the data before the drop.
onDropFromCache(i);

T removed = indexToRowMap.remove(Integer.valueOf(i));
keyToIndexMap.remove(getRowKey(removed));

onDropFromCache(i);
}
}

/**
* A hook that can be overridden to do something whenever a row is dropped
* from the cache.
* A hook that can be overridden to do something whenever a row is about to
* be dropped from the cache.
*
* @since 7.5.0
* @param rowIndex
@@ -732,4 +734,12 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
dataChangeHandler.resetDataAndSize(newSize);
}
}

protected int indexOfKey(Object rowKey) {
if (!keyToIndexMap.containsKey(rowKey)) {
return -1;
} else {
return keyToIndexMap.get(rowKey);
}
}
}

+ 1
- 1
server/build.xml Vedi File

@@ -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">

+ 97
- 273
server/src/com/vaadin/data/RpcDataProviderExtension.java Vedi File

@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -56,7 +57,6 @@ import com.vaadin.ui.Grid.RowReference;
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
@@ -91,34 +91,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
// private implementation
}

/**
* Sets the currently active rows. This will purge any unpinned rows
* from cache.
*
* @param itemIds
* collection of itemIds to map to row keys
*/
void setActiveRows(Collection<?> itemIds) {
Set<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) {
itemIdToKey.remove(itemId);
}

for (Object itemId : itemSet) {
itemIdToKey.put(itemId, getKey(itemId));
if (visibleDetails.contains(itemId)) {
detailComponentManager.createDetails(itemId);
}
}
}

private String nextKey() {
return String.valueOf(rollingIndex++);
}
@@ -277,246 +249,91 @@ public class RpcDataProviderExtension extends AbstractExtension {
public void generateData(Object itemId, Item item, JsonObject rowData) {
rowData.put(GridState.JSONKEY_ROWKEY, getKey(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).
* Removes all inactive item id to key mapping from the key mapper.
*
* @param firstActiveRow
* the first active row
* @param activeRowCount
* the number of active rows
* @since
*/
public void setActiveRows(Range newActiveRange) {

// TODO [[Components]] attach and detach components

/*-
* Example
*
* New Range: [3, 4, 5, 6, 7]
* Old Range: [1, 2, 3, 4, 5]
* Result: [1, 2][3, 4, 5] []
*/
final Range[] depractionPartition = activeRange
.partitionWith(newActiveRange);
removeValueChangeListeners(depractionPartition[0]);
removeValueChangeListeners(depractionPartition[2]);

/*-
* Example
*
* Old Range: [1, 2, 3, 4, 5]
* New Range: [3, 4, 5, 6, 7]
* Result: [] [3, 4, 5][6, 7]
*/
final Range[] activationPartition = newActiveRange
.partitionWith(activeRange);
addValueChangeListeners(activationPartition[0]);
addValueChangeListeners(activationPartition[2]);

activeRange = newActiveRange;

assert valueChangeListeners.size() == newActiveRange.length() : "Value change listeners not set up correctly!";
}

private void addValueChangeListeners(Range range) {
for (Integer i = range.getStart(); i < range.getEnd(); i++) {

final Object itemId = container.getIdByIndex(i);
final Item item = container.getItem(itemId);

assert valueChangeListeners.get(i) == null : "Overwriting existing listener";

GridValueChangeListener listener = new GridValueChangeListener(
itemId, item);
valueChangeListeners.put(i, listener);
public void dropInactiveItems() {
Collection<Object> active = activeItemHandler.getActiveItemIds();
Iterator<Object> itemIter = itemIdToKey.keySet().iterator();
while (itemIter.hasNext()) {
Object itemId = itemIter.next();
if (!active.contains(itemId) && !isPinned(itemId)) {
itemIter.remove();
}
}
}
}

private void removeValueChangeListeners(Range range) {
for (Integer i = range.getStart(); i < range.getEnd(); i++) {
final GridValueChangeListener listener = valueChangeListeners
.remove(i);
assert listener != null : "Trying to remove nonexisting listener";
/**
* Class for keeping track of current items and ValueChangeListeners.
*
* @since
*/
private class ActiveItemHandler implements Serializable {

listener.removeListener();
}
}
private final Map<Object, GridValueChangeListener> activeItemMap = new HashMap<Object, GridValueChangeListener>();
private final Set<Object> droppedItems = new HashSet<Object>();

/**
* Manages removed columns in active rows.
* <p>
* This method does <em>not</em> send data again to the client.
* Registers ValueChangeListeners for given items ids.
*
* @param removedColumns
* the columns that have been removed from the grid
* @param itemIds
* collection of new active item ids
*/
public void columnsRemoved(Collection<Column> removedColumns) {
if (removedColumns.isEmpty()) {
return;
public void addActiveItems(Collection<?> itemIds) {
for (Object itemId : itemIds) {
if (!activeItemMap.containsKey(itemId)) {
activeItemMap.put(itemId, new GridValueChangeListener(
itemId, container.getItem(itemId)));
}
}

for (GridValueChangeListener listener : valueChangeListeners
.values()) {
listener.removeColumns(removedColumns);
}
// Remove still active rows that were "dropped"
droppedItems.removeAll(itemIds);
dropListeners(droppedItems);
droppedItems.clear();
}

/**
* Manages added columns in active rows.
* <p>
* This method sends the data for the changed rows to client side.
* Marks given item id as dropped. Dropped items are cleared when adding
* new active items.
*
* @param addedColumns
* the columns that have been added to the grid
* @param itemId
* dropped item id
*/
public void columnsAdded(Collection<Column> addedColumns) {
if (addedColumns.isEmpty()) {
return;
public void dropActiveItem(Object itemId) {
if (activeItemMap.containsKey(itemId)) {
droppedItems.add(itemId);
}
}

private void dropListeners(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();
}
}

/**
* 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 */
}
}

/**
* Moves value change listeners in map with given index range by count
*/
private void moveListeners(Range movedRange, int diff) {
if (diff < 0) {
for (Integer i = movedRange.getStart(); i < movedRange.getEnd(); ++i) {
moveListener(i, i + diff);
}
} else if (diff > 0) {
for (Integer i = movedRange.getEnd() - 1; i >= movedRange
.getStart(); --i) {
moveListener(i, i + diff);
}
} else {
// diff == 0 should not happen. If it does, should be no-op
return;
}
}

private void moveListener(Integer oldIndex, Integer newIndex) {
assert valueChangeListeners.get(newIndex) == null : "Overwriting existing listener";

GridValueChangeListener listener = valueChangeListeners
.remove(oldIndex);
assert listener != null : "Moving nonexisting listener.";
valueChangeListeners.put(newIndex, listener);
public Collection<GridValueChangeListener> getValueChangeListeners() {
return new HashSet<GridValueChangeListener>(activeItemMap.values());
}
}

@@ -733,15 +550,20 @@ public class RpcDataProviderExtension extends AbstractExtension {
@Override
public void generateData(Object itemId, Item item, JsonObject rowData) {
if (visibleDetails.contains(itemId)) {
rowData.put(GridState.JSONKEY_DETAILS_VISIBLE, true);
// 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() {
@@ -802,14 +624,6 @@ 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();
}

listeners.clear();
activeRowHandler.activeRange = Range.withLength(0, 0);

for (Object itemId : visibleDetails) {
detailComponentManager.destroyDetails(itemId);
}
@@ -843,11 +657,13 @@ public class RpcDataProviderExtension extends AbstractExtension {
* This does not reflect the status in the DOM.
*/
// TODO this should probably be inside DetailComponentManager
private Set<Object> visibleDetails = new HashSet<Object>();
private final Set<Object> visibleDetails = new HashSet<Object>();

private final DetailComponentManager detailComponentManager = new DetailComponentManager();

private Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();

private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();

/**
* Creates a new data provider using the given container.
@@ -863,7 +679,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
@Override
public void requestRows(int firstRow, int numberOfRows,
int firstCachedRowIndex, int cacheSize) {

pushRowData(firstRow, numberOfRows, firstCachedRowIndex,
cacheSize);
}
@@ -881,6 +696,11 @@ public class RpcDataProviderExtension extends AbstractExtension {
keyMapper.unpin(itemId);
}
}

@Override
public void dropRow(String rowKey) {
activeItemHandler.dropActiveItem(keyMapper.getItemId(rowKey));
}
});

if (container instanceof ItemSetChangeNotifier) {
@@ -921,10 +741,9 @@ public class RpcDataProviderExtension extends AbstractExtension {

// Send current rows again if needed.
if (refreshCache) {
int firstRow = activeRowHandler.activeRange.getStart();
int numberOfRows = activeRowHandler.activeRange.length();

pushRowData(firstRow, numberOfRows, firstRow, numberOfRows);
for (Object itemId : activeItemHandler.getActiveItemIds()) {
internalUpdateRowData(itemId);
}
}
}

@@ -952,7 +771,6 @@ public class RpcDataProviderExtension extends AbstractExtension {

List<?> itemIds = container.getItemIds(fullRange.getStart(),
fullRange.length());
keyMapper.setActiveRows(itemIds);

JsonArray rows = Json.createArray();

@@ -964,14 +782,16 @@ public class RpcDataProviderExtension extends AbstractExtension {

for (int i = 0; i < newRange.length() && i + diff < itemIds.size(); ++i) {
Object itemId = itemIds.get(i + diff);

rows.set(i, getRowData(getGrid().getColumns(), itemId));
}
rpc.setRowData(firstRowToPush, rows);

activeRowHandler.setActiveRows(fullRange);
activeItemHandler.addActiveItems(itemIds);
keyMapper.dropInactiveItems();
}

private JsonValue getRowData(Collection<Column> columns, Object itemId) {
private JsonObject getRowData(Collection<Column> columns, Object itemId) {
Item item = container.getItem(itemId);

final JsonObject rowObject = Json.createObject();
@@ -1047,8 +867,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
rpc.insertRowData(index, count);
}
});

activeRowHandler.insertRows(index, count);
}

/**
@@ -1073,8 +891,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
rpc.removeRowData(index, count);
}
});

activeRowHandler.removeRows(index, count);
}

/**
@@ -1095,12 +911,9 @@ public class RpcDataProviderExtension extends AbstractExtension {
}

private void internalUpdateRowData(Object itemId) {
int index = container.indexOfId(itemId);
if (activeRowHandler.activeRange.contains(index)) {
JsonValue row = getRowData(getGrid().getColumns(), itemId);
JsonArray rowArray = Json.createArray();
rowArray.set(0, row);
rpc.setRowData(index, rowArray);
if (activeItemHandler.getActiveItemIds().contains(itemId)) {
JsonObject row = getRowData(getGrid().getColumns(), itemId);
rpc.updateRowData(row);
}
}

@@ -1118,9 +931,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
public void setParent(ClientConnector parent) {
if (parent == null) {
// We're being detached, release various listeners

activeRowHandler
.removeValueChangeListeners(activeRowHandler.activeRange);
activeItemHandler.dropListeners(activeItemHandler
.getActiveItemIds());

if (container instanceof ItemSetChangeNotifier) {
((ItemSetChangeNotifier) container)
@@ -1142,7 +954,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.
}

/**
@@ -1152,7 +970,13 @@ 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() {

+ 12
- 0
shared/src/com/vaadin/shared/data/DataProviderRpc.java Vedi File

@@ -20,6 +20,7 @@ import com.vaadin.shared.annotations.NoLayout;
import com.vaadin.shared.communication.ClientRpc;

import elemental.json.JsonArray;
import elemental.json.JsonObject;

/**
* RPC interface used for pushing container data to the client.
@@ -91,4 +92,15 @@ public interface DataProviderRpc extends ClientRpc {
* the size of the new data set
*/
public void resetDataAndSize(int size);

/**
* Informs the client that a row has updated. The client-side DataSource
* will map the given data to correct index if it should be in the cache.
*
* @since
* @param row
* the updated row data
*/
@NoLayout
public void updateRowData(JsonObject row);
}

+ 13
- 1
shared/src/com/vaadin/shared/data/DataRequestRpc.java Vedi File

@@ -16,8 +16,8 @@

package com.vaadin.shared.data;

import com.vaadin.shared.annotations.NoLoadingIndicator;
import com.vaadin.shared.annotations.Delayed;
import com.vaadin.shared.annotations.NoLoadingIndicator;
import com.vaadin.shared.communication.ServerRpc;

/**
@@ -55,5 +55,17 @@ public interface DataRequestRpc extends ServerRpc {
* pinned status of referenced item
*/
@Delayed
@NoLoadingIndicator
public void setPinned(String key, boolean isPinned);

/**
* Informs the server that an item is dropped from the client cache.
*
* @since
* @param rowKey
* key mapping to item
*/
@Delayed
@NoLoadingIndicator
public void dropRow(String rowKey);
}

+ 0
- 1
uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java Vedi File

@@ -56,7 +56,6 @@ public class GridDetailsDetach extends AbstractTestUI {

layout.addComponent(new Button("Reattach Grid",
new Button.ClickListener() {

@Override
public void buttonClick(ClickEvent event) {
gridContainer.removeAllComponents();

Loading…
Annulla
Salva