瀏覽代碼

Grid supports data set changes (#12645)

Change-Id: I5ceb52dea079f48b0065c1b2dbdc35b30fe8c4ee
tags/7.2.0.beta1
Henrik Paul 10 年之前
父節點
當前提交
7670a020c7

+ 83
- 1
client/src/com/vaadin/client/data/AbstractRemoteDataSource.java 查看文件

@@ -22,7 +22,7 @@ import java.util.List;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.vaadin.client.Profiler;
import com.vaadin.client.ui.grid.Range;
import com.vaadin.shared.ui.grid.Range;

/**
* Base implementation for data sources that fetch data from a remote system.
@@ -238,4 +238,86 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {

Profiler.leave("AbstractRemoteDataSource.setRowData");
}

/**
* Informs this data source that the server has removed data.
*
* @param firstRowIndex
* the index of the first removed row
* @param count
* the number of removed rows, starting from
* <code>firstRowIndex</code>
*/
protected void removeRowData(int firstRowIndex, int count) {
Profiler.enter("AbstractRemoteDataSource.removeRowData");

// pack the cached data
for (int i = 0; i < count; i++) {
Integer oldIndex = Integer.valueOf(firstRowIndex + count + i);
if (rowCache.containsKey(oldIndex)) {
Integer newIndex = Integer.valueOf(firstRowIndex + i);
rowCache.put(newIndex, rowCache.remove(oldIndex));
}
}

Range removedRange = Range.withLength(firstRowIndex, count);
if (removedRange.intersects(cached)) {
Range[] partitions = cached.partitionWith(removedRange);
Range remainsBefore = partitions[0];
Range transposedRemainsAfter = partitions[2].offsetBy(-removedRange
.length());
cached = remainsBefore.combineWith(transposedRemainsAfter);
}
estimatedSize -= count;
dataChangeHandler.dataRemoved(firstRowIndex, count);
checkCacheCoverage();

Profiler.leave("AbstractRemoteDataSource.removeRowData");
}

/**
* Informs this data source that new data has been inserted from the server.
*
* @param firstRowIndex
* the destination index of the new row data
* @param count
* the number of rows inserted
*/
protected void insertRowData(int firstRowIndex, int count) {
Profiler.enter("AbstractRemoteDataSource.insertRowData");

if (cached.contains(firstRowIndex)) {
int oldCacheEnd = cached.getEnd();
/*
* We need to invalidate the cache from the inserted row onwards,
* since the cache wants to be a contiguous range. It doesn't
* support holes.
*
* If holes were supported, we could shift the higher part of
* "cached" and leave a hole the size of "count" in the middle.
*/
cached = cached.splitAt(firstRowIndex)[0];

for (int i = firstRowIndex; i < oldCacheEnd; i++) {
rowCache.remove(Integer.valueOf(i));
}
}

else if (firstRowIndex < cached.getStart()) {
Range oldCached = cached;
cached = cached.offsetBy(count);

for (int i = 0; i < rowCache.size(); i++) {
Integer oldIndex = Integer.valueOf(oldCached.getEnd() - i);
Integer newIndex = Integer.valueOf(cached.getEnd() - i);
rowCache.put(newIndex, rowCache.remove(oldIndex));
}
}

estimatedSize += count;
dataChangeHandler.dataAdded(firstRowIndex, count);
checkCacheCoverage();

Profiler.leave("AbstractRemoteDataSource.insertRowData");
}
}

+ 10
- 0
client/src/com/vaadin/client/data/RpcDataSourceConnector.java 查看文件

@@ -56,6 +56,16 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
public void setRowData(int firstRow, List<String[]> rows) {
dataSource.setRowData(firstRow, rows);
}

@Override
public void removeRowData(int firstRow, int count) {
dataSource.removeRowData(firstRow, count);
}

@Override
public void insertRowData(int firstRow, int count) {
dataSource.insertRowData(firstRow, count);
}
});
}


+ 33
- 14
client/src/com/vaadin/client/ui/grid/Escalator.java 查看文件

@@ -35,12 +35,12 @@ import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.vaadin.client.Profiler;
import com.vaadin.client.Util;
import com.vaadin.client.ui.grid.Escalator.JsniUtil.TouchHandlerBundle;
@@ -50,6 +50,7 @@ import com.vaadin.client.ui.grid.PositionFunction.TranslatePosition;
import com.vaadin.client.ui.grid.PositionFunction.WebkitTranslate3DPosition;
import com.vaadin.client.ui.grid.ScrollbarBundle.HorizontalScrollbarBundle;
import com.vaadin.client.ui.grid.ScrollbarBundle.VerticalScrollbarBundle;
import com.vaadin.shared.ui.grid.Range;
import com.vaadin.shared.util.SharedUtil;

/*-
@@ -1633,6 +1634,8 @@ public class Escalator extends Widget {
return;
}

boolean rowsWereMoved = false;

final int topRowPos = getRowTop(visualRowOrder.getFirst());
// TODO [[mpixscroll]]
final int scrollTop = tBodyScrollTop;
@@ -1655,6 +1658,8 @@ public class Escalator extends Widget {
final int logicalRowIndex = scrollTop / ROW_HEIGHT_PX;
moveAndUpdateEscalatorRows(Range.between(start, end), 0,
logicalRowIndex);

rowsWereMoved = (rowsToMove != 0);
}

else if (viewportOffset + ROW_HEIGHT_PX <= 0) {
@@ -1723,9 +1728,13 @@ public class Escalator extends Widget {
.get(1)) - 1;
moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex);
}

rowsWereMoved = (rowsToMove != 0);
}

fireRowVisibilityChangeEvent();
if (rowsWereMoved) {
fireRowVisibilityChangeEvent();
}
}

@Override
@@ -1805,6 +1814,8 @@ public class Escalator extends Widget {
setRowPosition(tr, 0, rowTop);
rowTop += ROW_HEIGHT_PX;
}

fireRowVisibilityChangeEvent();
}
return addedRows;
}
@@ -1919,8 +1930,6 @@ public class Escalator extends Widget {
newRowTop += ROW_HEIGHT_PX;
}
}

fireRowVisibilityChangeEvent();
}

/**
@@ -3181,9 +3190,15 @@ public class Escalator extends Widget {
*/
@Override
public void setHeight(final String height) {
final int escalatorRowsBefore = body.visualRowOrder.size();

super.setHeight(height != null && !height.isEmpty() ? height
: DEFAULT_HEIGHT);
recalculateElementSizes();

if (escalatorRowsBefore != body.visualRowOrder.size()) {
fireRowVisibilityChangeEvent();
}
}

/**
@@ -3437,26 +3452,30 @@ public class Escalator extends Widget {
* Adds an event handler that gets notified when the range of visible rows
* changes e.g. because of scrolling.
*
* @param rowVisibilityChangeHadler
* @param rowVisibilityChangeHandler
* the event handler
* @return a handler registration for the added handler
*/
public HandlerRegistration addRowVisibilityChangeHandler(
RowVisibilityChangeHandler rowVisibilityChangeHadler) {
return addHandler(rowVisibilityChangeHadler,
RowVisibilityChangeHandler rowVisibilityChangeHandler) {
return addHandler(rowVisibilityChangeHandler,
RowVisibilityChangeEvent.TYPE);
}

private void fireRowVisibilityChangeEvent() {
int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder
.getFirst());
int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder
.getLast()) + 1;
if (!body.visualRowOrder.isEmpty()) {
int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder
.getFirst());
int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder
.getLast()) + 1;

int visibleRowCount = visibleRangeEnd - visibleRangeStart;
int visibleRowCount = visibleRangeEnd - visibleRangeStart;

fireEvent(new RowVisibilityChangeEvent(visibleRangeStart,
visibleRowCount));
fireEvent(new RowVisibilityChangeEvent(visibleRangeStart,
visibleRowCount));
} else {
fireEvent(new RowVisibilityChangeEvent(0, 0));
}
}

/**

+ 15
- 0
client/src/com/vaadin/client/ui/grid/Grid.java 查看文件

@@ -21,6 +21,7 @@ import java.util.List;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasVisibility;
import com.google.gwt.user.client.ui.Widget;
@@ -73,6 +74,10 @@ public class Grid<T> extends Composite {
*/
private final List<GridColumn<?, T>> columns = new ArrayList<GridColumn<?, T>>();

/**
* The datasource currently in use. <em>Note:</em> it is <code>null</code>
* on initialization, but not after that.
*/
private DataSource<T> dataSource;

/**
@@ -1211,4 +1216,14 @@ public class Grid<T> extends Composite {
public GridColumn<?, T> getLastFrozenColumn() {
return lastFrozenColumn;
}

public HandlerRegistration addRowVisibilityChangeHandler(
RowVisibilityChangeHandler handler) {
/*
* Reusing Escalator's RowVisibilityChangeHandler, since a scroll
* concept is too abstract. e.g. the event needs to be re-sent when the
* widget is resized.
*/
return escalator.addRowVisibilityChangeHandler(handler);
}
}

+ 16
- 0
client/src/com/vaadin/client/ui/grid/GridConnector.java 查看文件

@@ -30,6 +30,7 @@ import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.grid.ColumnGroupRowState;
import com.vaadin.shared.ui.grid.ColumnGroupState;
import com.vaadin.shared.ui.grid.GridColumnState;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;

/**
@@ -82,6 +83,21 @@ public class GridConnector extends AbstractComponentConnector {
return (GridState) super.getState();
}

@Override
protected void init() {
super.init();
getWidget().addRowVisibilityChangeHandler(
new RowVisibilityChangeHandler() {
@Override
public void onRowVisibilityChange(
RowVisibilityChangeEvent event) {
getRpcProxy(GridServerRpc.class).setVisibleRows(
event.getFirstVisibleRow(),
event.getVisibleRowCount());
}
});
}

@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);

+ 2
- 0
client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java 查看文件

@@ -21,6 +21,8 @@ import static org.junit.Assert.assertTrue;

import org.junit.Test;

import com.vaadin.shared.ui.grid.Range;

@SuppressWarnings("static-method")
public class PartitioningTest {


+ 59
- 12
server/src/com/vaadin/data/RpcDataProviderExtension.java 查看文件

@@ -18,6 +18,7 @@ package com.vaadin.data;

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

import com.vaadin.data.Container.Indexed;
@@ -67,22 +68,24 @@ public class RpcDataProviderExtension extends AbstractExtension {
Collection<?> propertyIds = container.getContainerPropertyIds();
List<String[]> rows = new ArrayList<String[]>(itemIds.size());
for (Object itemId : itemIds) {
Item item = container.getItem(itemId);
String[] row = new String[propertyIds.size()];

int i = 0;
for (Object propertyId : propertyIds) {
Object value = item.getItemProperty(propertyId).getValue();
String stringValue = String.valueOf(value);
row[i++] = stringValue;
}

rows.add(row);
rows.add(getRowData(propertyIds, itemId));
}

getRpcProxy(DataProviderRpc.class).setRowData(firstRow, rows);
}

private String[] getRowData(Collection<?> propertyIds, Object itemId) {
Item item = container.getItem(itemId);
String[] row = new String[propertyIds.size()];

int i = 0;
for (Object propertyId : propertyIds) {
Object value = item.getItemProperty(propertyId).getValue();
String stringValue = String.valueOf(value);
row[i++] = stringValue;
}
return row;
}

@Override
protected DataProviderState getState() {
return (DataProviderState) super.getState();
@@ -98,4 +101,48 @@ public class RpcDataProviderExtension extends AbstractExtension {
super.extend(component);
}

/**
* Informs the client side that new rows have been inserted into the data
* source.
*
* @param index
* the index at which new rows have been inserted
* @param count
* the number of rows inserted at <code>index</code>
*/
public void insertRowData(int index, int count) {
getState().containerSize += count;
getRpcProxy(DataProviderRpc.class).insertRowData(index, count);
}

/**
* Informs the client side that rows have been removed from the data source.
*
* @param firstIndex
* the index of the first row removed
* @param count
* the number of rows removed
*/
public void removeRowData(int firstIndex, int count) {
getState().containerSize -= count;
getRpcProxy(DataProviderRpc.class).removeRowData(firstIndex, count);
}

/**
* Informs the client side that data of a row has been modified in the data
* source.
*
* @param index
* the index of the row that was updated
*/
public void updateRowData(int index) {
/*
* TODO: ignore duplicate requests for the same index during the same
* roundtrip.
*/
Object itemId = container.getIdByIndex(index);
String[] row = getRowData(container.getContainerPropertyIds(), itemId);
getRpcProxy(DataProviderRpc.class).setRowData(index,
Collections.singletonList(row));
}
}

+ 361
- 1
server/src/com/vaadin/ui/components/grid/Grid.java 查看文件

@@ -26,15 +26,28 @@ import java.util.List;
import java.util.Map;

import com.vaadin.data.Container;
import com.vaadin.data.Container.Indexed.ItemAddEvent;
import com.vaadin.data.Container.Indexed.ItemRemoveEvent;
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.Item;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Property.ValueChangeNotifier;
import com.vaadin.data.RpcDataProviderExtension;
import com.vaadin.server.KeyMapper;
import com.vaadin.shared.ui.grid.ColumnGroupRowState;
import com.vaadin.shared.ui.grid.GridColumnState;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.Range;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;

/**
* Data grid component
@@ -56,6 +69,282 @@ import com.vaadin.ui.AbstractComponent;
*/
public class Grid extends AbstractComponent {

/**
* 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
* requried).
* <p>
* This bookeeping includes, but is not limited to:
* <ul>
* <li>listening to the currently visible {@link Property Properties'} value
* changes on the server side and sending those back to the client; and
* <li>attaching and detaching {@link Component Components} from the Vaadin
* Component hierarchy.
* </ul>
*/
private final class ActiveRowHandler {
/**
* A map from itemId to the value change listener used for all of its
* properties
*/
private final Map<Object, GridValueChangeListener> valueChangeListeners = new HashMap<Object, GridValueChangeListener>();

/**
* The currently active range. Practically, it's the range of row
* indices being displayed 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(int firstActiveRow, int activeRowCount) {

final Range newActiveRange = Range.withLength(firstActiveRow,
activeRowCount);

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

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

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

if (valueChangeListeners.containsKey(itemId)) {
/*
* This might occur when items are removed from above the
* viewport, the escalator scrolls up to compensate, but the
* same items remain in the view: It looks as if one row was
* scrolled, when in fact the whole viewport was shifted up.
*/
continue;
}

GridValueChangeListener listener = new GridValueChangeListener(
itemId);
valueChangeListeners.put(itemId, listener);

for (final Object propertyId : item.getItemPropertyIds()) {
final Property<?> property = item
.getItemProperty(propertyId);
if (property instanceof ValueChangeNotifier) {
((ValueChangeNotifier) property)
.addValueChangeListener(listener);
}
}
}
}

private void removeValueChangeListeners(Range range) {
for (int i = range.getStart(); i < range.getEnd(); i++) {
final Object itemId = datasource.getIdByIndex(i);
final Item item = datasource.getItem(itemId);
final GridValueChangeListener listener = valueChangeListeners
.remove(itemId);

if (listener != null) {
for (final Object propertyId : item.getItemPropertyIds()) {
final Property<?> property = item
.getItemProperty(propertyId);

/*
* Because listener != null, we can be certain that this
* property is a ValueChangeNotifier: It wouldn't be
* inserted in addValueChangeListeners if the property
* wasn't a suitable type. I.e. No need for "instanceof"
* check.
*/
((ValueChangeNotifier) property)
.removeValueChangeListener(listener);
}
}
}
}

public void clear() {
removeValueChangeListeners(activeRange);
/*
* we're doing an assert for emptiness there (instead of a
* carte-blanche ".clear()"), to be absolutely sure that everything
* is cleaned up properly, and that we have no dangling listeners.
*/
assert valueChangeListeners.isEmpty() : "GridValueChangeListeners are leaking";

activeRange = Range.withLength(0, 0);
}

/**
* Manages removed properties in active rows.
*
* @param removedPropertyIds
* the property ids that have been removed from the container
*/
public void propertiesRemoved(Collection<Object> removedPropertyIds) {
/*
* no-op, for now.
*
* The Container should be responsible for cleaning out any
* ValueChangeListeners from removed Properties. Components will
* benefit from this, however.
*/
}

/**
* Manages added properties in active rows.
*
* @param addedPropertyIds
* the property ids that have been added to the container
*/
public void propertiesAdded(Collection<Object> addedPropertyIds) {
for (int i = activeRange.getStart(); i < activeRange.getEnd(); i++) {
final Object itemId = datasource.getIdByIndex(i);
final Item item = datasource.getItem(itemId);
final GridValueChangeListener listener = valueChangeListeners
.get(itemId);
assert (listener != null) : "a listener should've been pre-made by addValueChangeListeners";

for (final Object propertyId : addedPropertyIds) {
final Property<?> property = item
.getItemProperty(propertyId);
if (property instanceof ValueChangeNotifier) {
((ValueChangeNotifier) property)
.addValueChangeListener(listener);
}
}
}
}

/**
* 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>
*
* @param firstIndex
* the index of the first inserted rows
* @param count
* the number of rows inserted at <code>firstIndex</code>
*/
public void insertRows(int firstIndex, int count) {
if (firstIndex < activeRange.getStart()) {
activeRange = activeRange.offsetBy(count);
} else if (firstIndex < activeRange.getEnd()) {
final Range deprecatedRange = Range.withLength(
activeRange.getEnd(), count);
removeValueChangeListeners(deprecatedRange);

final Range freshRange = Range.between(firstIndex, count);
addValueChangeListeners(freshRange);
} else {
// out of view, noop
}
}

/**
* Removes a single item by its id.
*
* @param itemId
* the id of the removed id. <em>Note:</em> this item does
* not exist anymore in the datasource
*/
public void removeItemId(Object itemId) {
final GridValueChangeListener removedListener = valueChangeListeners
.remove(itemId);
if (removedListener != null) {
/*
* We removed an item from somewhere in the visible range, so we
* make the active range shorter. The empty hole will be filled
* by the client-side code when it asks for more information.
*/
activeRange = Range.withLength(activeRange.getStart(),
activeRange.length() - 1);
}
}
}

/**
* A class to listen to changes in property values in the Container added
* with {@link Grid#setContainerDatasource(Container.Indexed)}, and notifies
* the data source to update the client-side representation of the modified
* item.
* <p>
* One instance of this class can (and should) be reused for all the
* properties in an item, since this class will inform that the entire row
* needs to be re-evaluated (in contrast to a property-based change
* management)
* <p>
* Since there's no Container-wide possibility to listen to any kind of
* value changes, an instance of this class needs to be attached to each and
* every Item's Property in the container.
*
* @see Grid#addValueChangeListener(Container, Object, Object)
* @see Grid#valueChangeListeners
*/
private class GridValueChangeListener implements ValueChangeListener {
private final Object itemId;

public GridValueChangeListener(Object itemId) {
/*
* Using an assert instead of an exception throw, just to optimize
* prematurely
*/
assert itemId != null : "null itemId not accepted";
this.itemId = itemId;
}

@Override
public void valueChange(ValueChangeEvent event) {
datasourceExtension.updateRowData(datasource.indexOfId(itemId));
}
}

/**
* The data source attached to the grid
*/
@@ -98,13 +387,17 @@ public class Grid extends AbstractComponent {
columnKeys.remove(columnId);
getState().columns.remove(column.getState());
}
activeRowHandler.propertiesRemoved(removedColumns);

// Add new columns
HashSet<Object> addedPropertyIds = new HashSet<Object>();
for (Object propertyId : properties) {
if (!columns.containsKey(propertyId)) {
appendColumn(propertyId);
addedPropertyIds.add(propertyId);
}
}
activeRowHandler.propertiesAdded(addedPropertyIds);

Object frozenPropertyId = columnKeys
.get(getState(false).lastFrozenColumnId);
@@ -114,8 +407,53 @@ public class Grid extends AbstractComponent {
}
};

private ItemSetChangeListener itemListener = new ItemSetChangeListener() {
@Override
public void containerItemSetChange(ItemSetChangeEvent event) {

if (event instanceof ItemAddEvent) {
ItemAddEvent addEvent = (ItemAddEvent) event;
int firstIndex = addEvent.getFirstIndex();
int count = addEvent.getAddedItemsCount();
datasourceExtension.insertRowData(firstIndex, count);
activeRowHandler.insertRows(firstIndex, count);
}

else if (event instanceof ItemRemoveEvent) {
ItemRemoveEvent removeEvent = (ItemRemoveEvent) event;
int firstIndex = removeEvent.getFirstIndex();
int count = removeEvent.getRemovedItemsCount();
datasourceExtension.removeRowData(firstIndex, count);

/*
* Unfortunately, there's no sane way of getting the rest of the
* removed itemIds.
*
* Fortunately, the only time _currently_ an event with more
* than one removed item seems to be when calling
* AbstractInMemoryContainer.removeAllElements(). Otherwise,
* it's only removing one item at a time.
*
* We _could_ have a backup of all the itemIds, and compare to
* that one, but we really really don't want to go there.
*/
activeRowHandler.removeItemId(removeEvent.getFirstItemId());
}

else {
// TODO no diff info available, redraw everything
throw new UnsupportedOperationException("bare "
+ "ItemSetChangeEvents are currently "
+ "not supported, use a container that "
+ "uses AddItemEvents and RemoveItemEvents.");
}
}
};

private RpcDataProviderExtension datasourceExtension;

private final ActiveRowHandler activeRowHandler = new ActiveRowHandler();

/**
* Creates a new Grid using the given datasource.
*
@@ -124,6 +462,14 @@ public class Grid extends AbstractComponent {
*/
public Grid(Container.Indexed datasource) {
setContainerDatasource(datasource);

registerRpc(new GridServerRpc() {
@Override
public void setVisibleRows(int firstVisibleRow, int visibleRowCount) {
activeRowHandler
.setActiveRows(firstVisibleRow, visibleRowCount);
}
});
}

/**
@@ -143,11 +489,16 @@ public class Grid extends AbstractComponent {
return;
}

// Remove old listener
// Remove old listeners
if (datasource instanceof PropertySetChangeNotifier) {
((PropertySetChangeNotifier) datasource)
.removePropertySetChangeListener(propertyListener);
}
if (datasource instanceof ItemSetChangeNotifier) {
((ItemSetChangeNotifier) datasource)
.removeItemSetChangeListener(itemListener);
}
activeRowHandler.clear();

if (datasourceExtension != null) {
removeExtension(datasourceExtension);
@@ -162,6 +513,15 @@ public class Grid extends AbstractComponent {
((PropertySetChangeNotifier) datasource)
.addPropertySetChangeListener(propertyListener);
}
if (datasource instanceof ItemSetChangeNotifier) {
((ItemSetChangeNotifier) datasource)
.addItemSetChangeListener(itemListener);
}
/*
* activeRowHandler will be updated by the client-side request that
* occurs on container change - no need to actively re-insert any
* ValueChangeListeners at this point.
*/

getState().columns.clear();
setLastFrozenPropertyId(null);

+ 21
- 0
shared/src/com/vaadin/shared/data/DataProviderRpc.java 查看文件

@@ -37,4 +37,25 @@ public interface DataProviderRpc extends ClientRpc {
* the updated row data
*/
public void setRowData(int firstRowIndex, List<String[]> rowData);

/**
* Informs the client to remove row data.
*
* @param firstRowIndex
* the index of the first removed row
* @param count
* the number of rows removed from <code>firstRowIndex</code> and
* onwards
*/
public void removeRowData(int firstRowIndex, int count);

/**
* Informs the client to insert new row data.
*
* @param firstRowIndex
* the index of the first new row
* @param count
* the number of rows inserted at <code>firstRowIndex</code>
*/
public void insertRowData(int firstRowIndex, int count);
}

+ 39
- 0
shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java 查看文件

@@ -0,0 +1,39 @@
/*
* Copyright 2000-2013 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.grid;

import com.vaadin.shared.communication.ServerRpc;

/**
* TODO
*
* @since 7.2
* @author Vaadin Ltd
*/
public interface GridServerRpc extends ServerRpc {

/**
* TODO
*
* @param firstVisibleRow
* the index of the first visible row
* @param visibleRowCount
* the number of rows visible, counted from
* <code>firstVisibleRow</code>
*/
void setVisibleRows(int firstVisibleRow, int visibleRowCount);

}

client/src/com/vaadin/client/ui/grid/Range.java → shared/src/com/vaadin/shared/ui/grid/Range.java 查看文件

@@ -14,7 +14,7 @@
* the License.
*/

package com.vaadin.client.ui.grid;
package com.vaadin.shared.ui.grid;

/**
* An immutable representation of a range, marked by start and end points.

client/tests/src/com/vaadin/client/ui/grid/RangeTest.java → shared/tests/src/com/vaadin/shared/ui/grid/RangeTest.java 查看文件

@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.client.ui.grid;
package com.vaadin.shared.ui.grid;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

+ 70
- 9
uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java 查看文件

@@ -40,20 +40,22 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {

private final int ROWS = 1000;

private IndexedContainer ds;

@Override
protected Grid constructComponent() {

// Build data source
IndexedContainer ds = new IndexedContainer();
ds = new IndexedContainer();

for (int col = 0; col < COLUMNS; col++) {
ds.addContainerProperty("Column" + col, String.class, "");
ds.addContainerProperty(getColumnProperty(col), String.class, "");
}

for (int row = 0; row < ROWS; row++) {
Item item = ds.addItem(Integer.valueOf(row));
for (int col = 0; col < COLUMNS; col++) {
item.getItemProperty("Column" + col).setValue(
item.getItemProperty(getColumnProperty(col)).setValue(
"(" + row + ", " + col + ")");
}
}
@@ -63,7 +65,8 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {

// Add footer values (header values are automatically created)
for (int col = 0; col < COLUMNS; col++) {
grid.getColumn("Column" + col).setFooterCaption("Footer " + col);
grid.getColumn(getColumnProperty(col)).setFooterCaption(
"Footer " + col);
}

// Set varying column widths
@@ -81,6 +84,8 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {

createColumnGroupActions();

createRowActions();

return grid;
}

@@ -131,9 +136,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
createCategory("Columns", null);

for (int c = 0; c < COLUMNS; c++) {
createCategory("Column" + c, "Columns");
createCategory(getColumnProperty(c), "Columns");

createBooleanAction("Visible", "Column" + c, true,
createBooleanAction("Visible", getColumnProperty(c), true,
new Command<Grid, Boolean>() {

@Override
@@ -148,7 +153,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
}, c);

createClickAction("Remove", "Column" + c,
createClickAction("Remove", getColumnProperty(c),
new Command<Grid, String>() {

@Override
@@ -158,7 +163,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
}, null, c);

createClickAction("Freeze", "Column" + c,
createClickAction("Freeze", getColumnProperty(c),
new Command<Grid, String>() {

@Override
@@ -167,7 +172,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
}, null, c);

createCategory("Column" + c + " Width", "Column" + c);
createCategory("Column" + c + " Width", getColumnProperty(c));

createClickAction("Auto", "Column" + c + " Width",
new Command<Grid, Integer>() {
@@ -203,6 +208,10 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
}

private static String getColumnProperty(int c) {
return "Column" + c;
}

protected void createColumnGroupActions() {
createCategory("Column groups", null);

@@ -269,6 +278,58 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {

}

protected void createRowActions() {
createCategory("Body rows", null);

createClickAction("Add first row", "Body rows",
new Command<Grid, String>() {
@Override
public void execute(Grid c, String value, Object data) {
Item item = ds.addItemAt(0, new Object());
for (int i = 0; i < COLUMNS; i++) {
item.getItemProperty(getColumnProperty(i))
.setValue("newcell: " + i);
}
}
}, null);

createClickAction("Remove first row", "Body rows",
new Command<Grid, String>() {
@Override
public void execute(Grid c, String value, Object data) {
Object firstItemId = ds.getIdByIndex(0);
ds.removeItem(firstItemId);
}
}, null);

createClickAction("Modify first row (getItemProperty)", "Body rows",
new Command<Grid, String>() {
@Override
public void execute(Grid c, String value, Object data) {
Object firstItemId = ds.getIdByIndex(0);
Item item = ds.getItem(firstItemId);
for (int i = 0; i < COLUMNS; i++) {
item.getItemProperty(getColumnProperty(i))
.setValue("modified: " + i);
}
}
}, null);

createClickAction("Modify first row (getContainerProperty)",
"Body rows", new Command<Grid, String>() {
@Override
public void execute(Grid c, String value, Object data) {
Object firstItemId = ds.getIdByIndex(0);
for (Object containerPropertyId : ds
.getContainerPropertyIds()) {
ds.getContainerProperty(firstItemId,
containerPropertyId).setValue(
"modified: " + containerPropertyId);
}
}
}, null);
}

@Override
protected Integer getTicketNumber() {
return 12829;

+ 50
- 0
uitest/src/com/vaadin/tests/components/grid/GridBasicFeaturesTest.java 查看文件

@@ -22,6 +22,7 @@ import java.util.List;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;

@@ -253,6 +254,55 @@ public class GridBasicFeaturesTest extends MultiBrowserTest {
assertPrimaryStylename("v-grid");
}

/**
* Test that the current view is updated when a server-side container change
* occurs (without scrolling back and forth)
*/
@Test
public void testItemSetChangeEvent() throws Exception {
openTestURL();

final By newRow = By.xpath("//td[text()='newcell: 0']");

assertTrue("Unexpected initial state", !elementIsFound(newRow));

selectMenuPath("Component", "Body rows", "Add first row");
assertTrue("Add row failed", elementIsFound(newRow));

selectMenuPath("Component", "Body rows", "Remove first row");
assertTrue("Remove row failed", !elementIsFound(newRow));
}

/**
* Test that the current view is updated when a property's value is reflect
* to the client, when the value is modified server-side.
*/
@Test
public void testPropertyValueChangeEvent() throws Exception {
openTestURL();

assertEquals("Unexpected cell initial state", "(0, 0)",
getBodyCellByRowAndColumn(1, 1).getText());

selectMenuPath("Component", "Body rows",
"Modify first row (getItemProperty)");
assertEquals("(First) modification with getItemProperty failed",
"modified: 0", getBodyCellByRowAndColumn(1, 1).getText());

selectMenuPath("Component", "Body rows",
"Modify first row (getContainerProperty)");
assertEquals("(Second) modification with getItemProperty failed",
"modified: Column0", getBodyCellByRowAndColumn(1, 1).getText());
}

private boolean elementIsFound(By locator) {
try {
return driver.findElement(locator) != null;
} catch (NoSuchElementException e) {
return false;
}
}

private void assertPrimaryStylename(String stylename) {
assertTrue(getGridElement().getAttribute("class").contains(stylename));


Loading…
取消
儲存