Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.vaadin.client.connectors.GridConnector Maven / Gradle / Ivy
Go to download
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright 2000-2014 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.client.connectors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.DeferredWorker;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener;
import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
import com.vaadin.client.data.DataSource.RowHandle;
import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.AbstractHasComponentsConnector;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.CellStyleGenerator;
import com.vaadin.client.widget.grid.DetailsGenerator;
import com.vaadin.client.widget.grid.EditorHandler;
import com.vaadin.client.widget.grid.RowReference;
import com.vaadin.client.widget.grid.RowStyleGenerator;
import com.vaadin.client.widget.grid.events.BodyClickHandler;
import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
import com.vaadin.client.widget.grid.events.GridClickEvent;
import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
import com.vaadin.client.widget.grid.events.SelectAllEvent;
import com.vaadin.client.widget.grid.events.SelectAllHandler;
import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel;
import com.vaadin.client.widget.grid.selection.SelectionEvent;
import com.vaadin.client.widget.grid.selection.SelectionHandler;
import com.vaadin.client.widget.grid.selection.SelectionModel;
import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
import com.vaadin.client.widget.grid.selection.SelectionModelNone;
import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
import com.vaadin.client.widget.grid.sort.SortEvent;
import com.vaadin.client.widget.grid.sort.SortHandler;
import com.vaadin.client.widget.grid.sort.SortOrder;
import com.vaadin.client.widgets.Grid;
import com.vaadin.client.widgets.Grid.Column;
import com.vaadin.client.widgets.Grid.FooterCell;
import com.vaadin.client.widgets.Grid.FooterRow;
import com.vaadin.client.widgets.Grid.HeaderCell;
import com.vaadin.client.widgets.Grid.HeaderRow;
import com.vaadin.shared.Connector;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.grid.DetailsConnectorChange;
import com.vaadin.shared.ui.grid.EditorClientRpc;
import com.vaadin.shared.ui.grid.EditorServerRpc;
import com.vaadin.shared.ui.grid.GridClientRpc;
import com.vaadin.shared.ui.grid.GridColumnState;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode;
import com.vaadin.shared.ui.grid.GridStaticSectionState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
import com.vaadin.shared.ui.grid.ScrollDestination;
import elemental.json.JsonObject;
import elemental.json.JsonValue;
/**
* Connects the client side {@link Grid} widget with the server side
* {@link com.vaadin.ui.components.grid.Grid} component.
*
* The Grid is typed to JSONObject. The structure of the JSONObject is described
* at {@link com.vaadin.shared.data.DataProviderRpc#setRowData(int, List)
* DataProviderRpc.setRowData(int, List)}.
*
* @since 7.4
* @author Vaadin Ltd
*/
@Connect(com.vaadin.ui.Grid.class)
public class GridConnector extends AbstractHasComponentsConnector implements
SimpleManagedLayout, DeferredWorker {
private static final class CustomCellStyleGenerator implements
CellStyleGenerator {
@Override
public String getStyle(CellReference cellReference) {
JsonObject row = cellReference.getRow();
if (!row.hasKey(GridState.JSONKEY_CELLSTYLES)) {
return null;
}
Column, JsonObject> column = cellReference.getColumn();
if (!(column instanceof CustomGridColumn)) {
// Selection checkbox column
return null;
}
CustomGridColumn c = (CustomGridColumn) column;
JsonObject cellStylesObject = row
.getObject(GridState.JSONKEY_CELLSTYLES);
assert cellStylesObject != null;
if (cellStylesObject.hasKey(c.id)) {
return cellStylesObject.getString(c.id);
} else {
return null;
}
}
}
private static final class CustomRowStyleGenerator implements
RowStyleGenerator {
@Override
public String getStyle(RowReference rowReference) {
JsonObject row = rowReference.getRow();
if (row.hasKey(GridState.JSONKEY_ROWSTYLE)) {
return row.getString(GridState.JSONKEY_ROWSTYLE);
} else {
return null;
}
}
}
/**
* Custom implementation of the custom grid column using a JSONObject to
* represent the cell value and String as a column type.
*/
private class CustomGridColumn extends Grid.Column {
private final String id;
private AbstractRendererConnector rendererConnector;
private AbstractFieldConnector editorConnector;
public CustomGridColumn(String id,
AbstractRendererConnector rendererConnector) {
super(rendererConnector.getRenderer());
this.rendererConnector = rendererConnector;
this.id = id;
}
/**
* Sets a new renderer for this column object
*
* @param rendererConnector
* a renderer connector object
*/
public void setRenderer(
AbstractRendererConnector rendererConnector) {
setRenderer(rendererConnector.getRenderer());
this.rendererConnector = rendererConnector;
}
@Override
public Object getValue(final JsonObject obj) {
final JsonObject rowData = obj.getObject(GridState.JSONKEY_DATA);
if (rowData.hasKey(id)) {
final JsonValue columnValue = rowData.get(id);
return rendererConnector.decode(columnValue);
}
return null;
}
private AbstractFieldConnector getEditorConnector() {
return editorConnector;
}
private void setEditorConnector(AbstractFieldConnector editorConnector) {
this.editorConnector = editorConnector;
}
}
/*
* An editor handler using Vaadin RPC to manage the editor state.
*/
private class CustomEditorHandler implements EditorHandler {
private EditorServerRpc rpc = getRpcProxy(EditorServerRpc.class);
private EditorRequest currentRequest = null;
private boolean serverInitiated = false;
public CustomEditorHandler() {
registerRpc(EditorClientRpc.class, new EditorClientRpc() {
@Override
public void bind(final int rowIndex) {
// call this finally to avoid issues with editing on init
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
GridConnector.this.getWidget().editRow(rowIndex);
}
});
}
@Override
public void cancel(int rowIndex) {
serverInitiated = true;
GridConnector.this.getWidget().cancelEditor();
}
@Override
public void confirmBind(final boolean bindSucceeded) {
endRequest(bindSucceeded, null, null);
}
@Override
public void confirmSave(boolean saveSucceeded,
String errorMessage, List errorColumnsIds) {
endRequest(saveSucceeded, errorMessage, errorColumnsIds);
}
});
}
@Override
public void bind(EditorRequest request) {
startRequest(request);
rpc.bind(request.getRowIndex());
}
@Override
public void save(EditorRequest request) {
startRequest(request);
rpc.save(request.getRowIndex());
}
@Override
public void cancel(EditorRequest request) {
if (!handleServerInitiated(request)) {
// No startRequest as we don't get (or need)
// a confirmation from the server
rpc.cancel(request.getRowIndex());
}
}
@Override
public Widget getWidget(Grid.Column, JsonObject> column) {
assert column != null;
if (column instanceof CustomGridColumn) {
AbstractFieldConnector c = ((CustomGridColumn) column)
.getEditorConnector();
return c != null ? c.getWidget() : null;
} else {
throw new IllegalStateException("Unexpected column type: "
+ column.getClass().getName());
}
}
/**
* Used to handle the case where the editor calls us because it was
* invoked by the server via RPC and not by the client. In that case,
* the request can be simply synchronously completed.
*
* @param request
* the request object
* @return true if the request was originally triggered by the server,
* false otherwise
*/
private boolean handleServerInitiated(EditorRequest> request) {
assert request != null : "Cannot handle null request";
assert currentRequest == null : "Earlier request not yet finished";
if (serverInitiated) {
serverInitiated = false;
request.success();
return true;
} else {
return false;
}
}
private void startRequest(EditorRequest request) {
assert currentRequest == null : "Earlier request not yet finished";
currentRequest = request;
}
private void endRequest(boolean succeeded, String errorMessage,
List errorColumnsIds) {
assert currentRequest != null : "Current request was null";
/*
* Clear current request first to ensure the state is valid if
* another request is made in the callback.
*/
EditorRequest request = currentRequest;
currentRequest = null;
if (succeeded) {
request.success();
} else {
Collection> errorColumns;
if (errorColumnsIds != null) {
errorColumns = new ArrayList>();
for (String colId : errorColumnsIds) {
errorColumns.add(columnIdToColumn.get(colId));
}
} else {
errorColumns = null;
}
request.failure(errorMessage, errorColumns);
}
}
}
private class ItemClickHandler implements BodyClickHandler,
BodyDoubleClickHandler {
@Override
public void onClick(GridClickEvent event) {
if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
fireItemClick(event.getTargetCell(), event.getNativeEvent());
}
}
@Override
public void onDoubleClick(GridDoubleClickEvent event) {
if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
fireItemClick(event.getTargetCell(), event.getNativeEvent());
}
}
private void fireItemClick(CellReference> cell, NativeEvent mouseEvent) {
String rowKey = getRowKey((JsonObject) cell.getRow());
String columnId = getColumnId(cell.getColumn());
getRpcProxy(GridServerRpc.class)
.itemClick(
rowKey,
columnId,
MouseEventDetailsBuilder
.buildMouseEventDetails(mouseEvent));
}
}
private ColumnReorderHandler columnReorderHandler = new ColumnReorderHandler() {
@Override
public void onColumnReorder(ColumnReorderEvent event) {
if (!columnsUpdatedFromState) {
List> columns = getWidget().getColumns();
final List newColumnOrder = new ArrayList();
for (Column, JsonObject> column : columns) {
if (column instanceof CustomGridColumn) {
newColumnOrder.add(((CustomGridColumn) column).id);
} // the other case would be the multi selection column
}
getRpcProxy(GridServerRpc.class).columnsReordered(
newColumnOrder, columnOrder);
columnOrder = newColumnOrder;
getState().columnOrder = newColumnOrder;
}
}
};
private ColumnVisibilityChangeHandler columnVisibilityChangeHandler = new ColumnVisibilityChangeHandler() {
@Override
public void onVisibilityChange(
ColumnVisibilityChangeEvent event) {
if (!columnsUpdatedFromState) {
Column, JsonObject> column = event.getColumn();
if (column instanceof CustomGridColumn) {
getRpcProxy(GridServerRpc.class).columnVisibilityChanged(
((CustomGridColumn) column).id, column.isHidden(),
event.isUserOriginated());
for (GridColumnState state : getState().columns) {
if (state.id.equals(((CustomGridColumn) column).id)) {
state.hidden = event.isHidden();
break;
}
}
} else {
getLogger().warning(
"Visibility changed for a unknown column type in Grid: "
+ column.toString() + ", type "
+ column.getClass());
}
}
}
};
private static class CustomDetailsGenerator implements DetailsGenerator {
private final Map indexToDetailsMap = new HashMap();
@Override
@SuppressWarnings("boxing")
public Widget getDetails(int rowIndex) {
ComponentConnector componentConnector = indexToDetailsMap
.get(rowIndex);
if (componentConnector != null) {
return componentConnector.getWidget();
} else {
return null;
}
}
public void setDetailsConnectorChanges(
Set changes) {
/*
* To avoid overwriting connectors while moving them about, we'll
* take all the affected connectors, first all remove those that are
* removed or moved, then we add back those that are moved or added.
*/
/* Remove moved/removed connectors from bookkeeping */
for (DetailsConnectorChange change : changes) {
Integer oldIndex = change.getOldIndex();
Connector removedConnector = indexToDetailsMap.remove(oldIndex);
Connector connector = change.getConnector();
assert removedConnector == null || connector == null
|| removedConnector.equals(connector) : "Index "
+ oldIndex + " points to " + removedConnector
+ " while " + connector + " was expected";
}
/* Add moved/added connectors to bookkeeping */
for (DetailsConnectorChange change : changes) {
Integer newIndex = change.getNewIndex();
ComponentConnector connector = (ComponentConnector) change
.getConnector();
if (connector != null) {
assert newIndex != null : "An existing connector has a missing new index.";
ComponentConnector prevConnector = indexToDetailsMap.put(
newIndex, connector);
assert prevConnector == null : "Connector collision at index "
+ newIndex
+ " between old "
+ prevConnector
+ " and new " + connector;
}
}
}
}
@SuppressWarnings("boxing")
private static class DetailsConnectorFetcher implements DeferredWorker {
private static final int FETCH_TIMEOUT_MS = 5000;
public interface Listener {
void fetchHasBeenScheduled(int id);
void fetchHasReturned(int id);
}
/** A flag making sure that we don't call scheduleFinally many times. */
private boolean fetcherHasBeenCalled = false;
/** A rolling counter for unique values. */
private int detailsFetchCounter = 0;
/** A collection that tracks the amount of requests currently underway. */
private Set pendingFetches = new HashSet(5);
private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() {
@Override
public void execute() {
int currentFetchId = detailsFetchCounter++;
pendingFetches.add(currentFetchId);
rpc.sendDetailsComponents(currentFetchId);
fetcherHasBeenCalled = false;
if (listener != null) {
listener.fetchHasBeenScheduled(currentFetchId);
}
assert assertRequestDoesNotTimeout(currentFetchId);
}
};
private DetailsConnectorFetcher.Listener listener = null;
private final GridServerRpc rpc;
public DetailsConnectorFetcher(GridServerRpc rpc) {
assert rpc != null : "RPC was null";
this.rpc = rpc;
}
public void schedule() {
if (!fetcherHasBeenCalled) {
Scheduler.get().scheduleFinally(lazyDetailsFetcher);
fetcherHasBeenCalled = true;
}
}
public void responseReceived(int fetchId) {
if (fetchId < 0) {
/* Ignore negative fetchIds (they're pushed, not fetched) */
return;
}
boolean success = pendingFetches.remove(fetchId);
assert success : "Received a response with an unidentified fetch id";
if (listener != null) {
listener.fetchHasReturned(fetchId);
}
}
@Override
public boolean isWorkPending() {
return fetcherHasBeenCalled || !pendingFetches.isEmpty();
}
private boolean assertRequestDoesNotTimeout(final int fetchId) {
/*
* This method will not be compiled without asserts enabled. This
* only makes sure that any request does not time out.
*
* TODO Should this be an explicit check? Is it worth the overhead?
*/
new Timer() {
@Override
public void run() {
assert !pendingFetches.contains(fetchId) : "Fetch id "
+ fetchId + " timed out.";
}
}.schedule(FETCH_TIMEOUT_MS);
return true;
}
public void setListener(DetailsConnectorFetcher.Listener listener) {
// if more are needed, feel free to convert this into a collection.
this.listener = listener;
}
}
/**
* The functionality that makes sure that the scroll position is still kept
* up-to-date even if more details are being fetched lazily.
*/
private class LazyDetailsScrollAdjuster implements DeferredWorker {
private static final int SCROLL_TO_END_ID = -2;
private static final int NO_SCROLL_SCHEDULED = -1;
private class ScrollStopChecker implements DeferredWorker {
private final ScheduledCommand checkCommand = new ScheduledCommand() {
@Override
public void execute() {
isScheduled = false;
if (queuedFetches.isEmpty()) {
currentRow = NO_SCROLL_SCHEDULED;
destination = null;
}
}
};
private boolean isScheduled = false;
public void schedule() {
if (isScheduled) {
return;
}
Scheduler.get().scheduleDeferred(checkCommand);
isScheduled = true;
}
@Override
public boolean isWorkPending() {
return isScheduled;
}
}
private DetailsConnectorFetcher.Listener fetcherListener = new DetailsConnectorFetcher.Listener() {
@Override
@SuppressWarnings("boxing")
public void fetchHasBeenScheduled(int id) {
if (currentRow != NO_SCROLL_SCHEDULED) {
queuedFetches.add(id);
}
}
@Override
@SuppressWarnings("boxing")
public void fetchHasReturned(int id) {
if (currentRow == NO_SCROLL_SCHEDULED
|| queuedFetches.isEmpty()) {
return;
}
queuedFetches.remove(id);
if (currentRow == SCROLL_TO_END_ID) {
getWidget().scrollToEnd();
} else {
getWidget().scrollToRow(currentRow, destination);
}
/*
* Schedule a deferred call whether we should stop adjusting for
* scrolling.
*
* This is done deferredly just because we can't be absolutely
* certain whether this most recent scrolling won't cascade into
* further lazy details loading (perhaps deferredly).
*/
scrollStopChecker.schedule();
}
};
private int currentRow = NO_SCROLL_SCHEDULED;
private final Set queuedFetches = new HashSet();
private final ScrollStopChecker scrollStopChecker = new ScrollStopChecker();
private ScrollDestination destination;
public LazyDetailsScrollAdjuster() {
detailsConnectorFetcher.setListener(fetcherListener);
}
public void adjustForEnd() {
currentRow = SCROLL_TO_END_ID;
}
public void adjustFor(int row, ScrollDestination destination) {
currentRow = row;
this.destination = destination;
}
@Override
public boolean isWorkPending() {
return currentRow != NO_SCROLL_SCHEDULED
|| !queuedFetches.isEmpty()
|| scrollStopChecker.isWorkPending();
}
}
/**
* Maps a generated column id to a grid column instance
*/
private Map columnIdToColumn = new HashMap();
private AbstractRowHandleSelectionModel selectionModel;
private Set selectedKeys = new LinkedHashSet();
private List columnOrder = new ArrayList();
/**
* {@link #selectionUpdatedFromState} is set to true when
* {@link #updateSelectionFromState()} makes changes to selection. This flag
* tells the {@code internalSelectionChangeHandler} to not send same data
* straight back to server. Said listener sets it back to false when
* handling that event.
*/
private boolean selectionUpdatedFromState;
/**
* {@link #columnsUpdatedFromState} is set to true when
* {@link #updateColumnOrderFromState(List)} is updating the column order
* for the widget. This flag tells the {@link #columnReorderHandler} to not
* send same data straight back to server. After updates, listener sets the
* value back to false.
*/
private boolean columnsUpdatedFromState;
private RpcDataSource dataSource;
private SelectionHandler internalSelectionChangeHandler = new SelectionHandler() {
@Override
public void onSelect(SelectionEvent event) {
if (event.isBatchedSelection()) {
return;
}
if (!selectionUpdatedFromState) {
for (JsonObject row : event.getRemoved()) {
selectedKeys.remove(dataSource.getRowKey(row));
}
for (JsonObject row : event.getAdded()) {
selectedKeys.add(dataSource.getRowKey(row));
}
getRpcProxy(GridServerRpc.class).select(
new ArrayList(selectedKeys));
} else {
selectionUpdatedFromState = false;
}
}
};
private ItemClickHandler itemClickHandler = new ItemClickHandler();
private String lastKnownTheme = null;
private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator();
private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher(
getRpcProxy(GridServerRpc.class));
private final DetailsListener detailsListener = new DetailsListener() {
@Override
public void reapplyDetailsVisibility(int rowIndex, JsonObject row) {
if (hasDetailsOpen(row)) {
getWidget().setDetailsVisible(rowIndex, true);
detailsConnectorFetcher.schedule();
} else {
getWidget().setDetailsVisible(rowIndex, false);
}
}
private boolean hasDetailsOpen(JsonObject row) {
return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
&& row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE);
}
@Override
public void closeDetails(int rowIndex) {
getWidget().setDetailsVisible(rowIndex, false);
}
};
private final LazyDetailsScrollAdjuster lazyDetailsScrollAdjuster = new LazyDetailsScrollAdjuster();
@Override
@SuppressWarnings("unchecked")
public Grid getWidget() {
return (Grid) super.getWidget();
}
@Override
public GridState getState() {
return (GridState) super.getState();
}
@Override
protected void init() {
super.init();
// All scroll RPC calls are executed finally to avoid issues on init
registerRpc(GridClientRpc.class, new GridClientRpc() {
@Override
public void scrollToStart() {
/*
* no need for lazyDetailsScrollAdjuster, because the start is
* always 0, won't change a bit.
*/
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
getWidget().scrollToStart();
}
});
}
@Override
public void scrollToEnd() {
lazyDetailsScrollAdjuster.adjustForEnd();
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
getWidget().scrollToEnd();
}
});
}
@Override
public void scrollToRow(final int row,
final ScrollDestination destination) {
lazyDetailsScrollAdjuster.adjustFor(row, destination);
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
getWidget().scrollToRow(row, destination);
}
});
}
@Override
public void recalculateColumnWidths() {
getWidget().recalculateColumnWidths();
}
@Override
@SuppressWarnings("boxing")
public void setDetailsConnectorChanges(
Set connectorChanges, int fetchId) {
customDetailsGenerator
.setDetailsConnectorChanges(connectorChanges);
List removedFirst = new ArrayList(
connectorChanges);
Collections.sort(removedFirst,
DetailsConnectorChange.REMOVED_FIRST_COMPARATOR);
// refresh moved/added details rows
for (DetailsConnectorChange change : removedFirst) {
Integer oldIndex = change.getOldIndex();
Integer newIndex = change.getNewIndex();
assert oldIndex == null || oldIndex >= 0 : "Got an "
+ "invalid old index: " + oldIndex
+ " (connector: " + change.getConnector() + ")";
assert newIndex == null || newIndex >= 0 : "Got an "
+ "invalid new index: " + newIndex
+ " (connector: " + change.getConnector() + ")";
if (oldIndex != null) {
/* Close the old/removed index */
getWidget().setDetailsVisible(oldIndex, false);
if (change.isShouldStillBeVisible()) {
getWidget().setDetailsVisible(oldIndex, true);
}
}
if (newIndex != null) {
/*
* Since the component was lazy loaded, we need to
* refresh the details by toggling it.
*/
getWidget().setDetailsVisible(newIndex, false);
getWidget().setDetailsVisible(newIndex, true);
}
}
detailsConnectorFetcher.responseReceived(fetchId);
}
});
getWidget().addSelectionHandler(internalSelectionChangeHandler);
/* Item click events */
getWidget().addBodyClickHandler(itemClickHandler);
getWidget().addBodyDoubleClickHandler(itemClickHandler);
getWidget().addSortHandler(new SortHandler() {
@Override
public void sort(SortEvent event) {
List order = event.getOrder();
String[] columnIds = new String[order.size()];
SortDirection[] directions = new SortDirection[order.size()];
for (int i = 0; i < order.size(); i++) {
SortOrder sortOrder = order.get(i);
CustomGridColumn column = (CustomGridColumn) sortOrder
.getColumn();
columnIds[i] = column.id;
directions[i] = sortOrder.getDirection();
}
if (!Arrays.equals(columnIds, getState().sortColumns)
|| !Arrays.equals(directions, getState().sortDirs)) {
// Report back to server if changed
getRpcProxy(GridServerRpc.class).sort(columnIds,
directions, event.isUserOriginated());
}
}
});
getWidget().addSelectAllHandler(new SelectAllHandler() {
@Override
public void onSelectAll(SelectAllEvent event) {
getRpcProxy(GridServerRpc.class).selectAll();
}
});
getWidget().setEditorHandler(new CustomEditorHandler());
getWidget().addColumnReorderHandler(columnReorderHandler);
getWidget().addColumnVisibilityChangeHandler(
columnVisibilityChangeHandler);
getWidget().setDetailsGenerator(customDetailsGenerator);
getLayoutManager().registerDependency(this, getWidget().getElement());
layout();
}
@Override
public void onStateChanged(final StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
// Column updates
if (stateChangeEvent.hasPropertyChanged("columns")) {
// Remove old columns
purgeRemovedColumns();
// Add new columns
for (GridColumnState state : getState().columns) {
if (!columnIdToColumn.containsKey(state.id)) {
addColumnFromStateChangeEvent(state);
}
updateColumnFromStateChangeEvent(state);
}
}
if (stateChangeEvent.hasPropertyChanged("columnOrder")) {
if (orderNeedsUpdate(getState().columnOrder)) {
updateColumnOrderFromState(getState().columnOrder);
}
}
// Header and footer
if (stateChangeEvent.hasPropertyChanged("header")) {
updateHeaderFromState(getState().header);
}
if (stateChangeEvent.hasPropertyChanged("footer")) {
updateFooterFromState(getState().footer);
}
// Selection
if (stateChangeEvent.hasPropertyChanged("selectionMode")) {
onSelectionModeChange();
updateSelectDeselectAllowed();
} else if (stateChangeEvent
.hasPropertyChanged("singleSelectDeselectAllowed")) {
updateSelectDeselectAllowed();
}
if (stateChangeEvent.hasPropertyChanged("selectedKeys")) {
updateSelectionFromState();
}
// Sorting
if (stateChangeEvent.hasPropertyChanged("sortColumns")
|| stateChangeEvent.hasPropertyChanged("sortDirs")) {
onSortStateChange();
}
// Editor
if (stateChangeEvent.hasPropertyChanged("editorEnabled")) {
getWidget().setEditorEnabled(getState().editorEnabled);
}
// Frozen columns
if (stateChangeEvent.hasPropertyChanged("frozenColumnCount")) {
getWidget().setFrozenColumnCount(getState().frozenColumnCount);
}
// Theme features
String activeTheme = getConnection().getUIConnector().getActiveTheme();
if (lastKnownTheme == null) {
lastKnownTheme = activeTheme;
} else if (!lastKnownTheme.equals(activeTheme)) {
getWidget().resetSizesFromDom();
lastKnownTheme = activeTheme;
}
}
private void updateSelectDeselectAllowed() {
SelectionModel model = getWidget().getSelectionModel();
if (model instanceof SelectionModel.Single>) {
((SelectionModel.Single>) model)
.setDeselectAllowed(getState().singleSelectDeselectAllowed);
}
}
private void updateColumnOrderFromState(List stateColumnOrder) {
CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder
.size()];
int i = 0;
for (String id : stateColumnOrder) {
columns[i] = columnIdToColumn.get(id);
i++;
}
columnsUpdatedFromState = true;
getWidget().setColumnOrder(columns);
columnsUpdatedFromState = false;
columnOrder = stateColumnOrder;
}
private boolean orderNeedsUpdate(List stateColumnOrder) {
if (stateColumnOrder.size() == columnOrder.size()) {
for (int i = 0; i < columnOrder.size(); ++i) {
if (!stateColumnOrder.get(i).equals(columnOrder.get(i))) {
return true;
}
}
return false;
}
return true;
}
private void updateHeaderFromState(GridStaticSectionState state) {
getWidget().setHeaderVisible(state.visible);
while (getWidget().getHeaderRowCount() > 0) {
getWidget().removeHeaderRow(0);
}
for (RowState rowState : state.rows) {
HeaderRow row = getWidget().appendHeaderRow();
for (CellState cellState : rowState.cells) {
CustomGridColumn column = columnIdToColumn
.get(cellState.columnId);
updateHeaderCellFromState(row.getCell(column), cellState);
}
for (Set group : rowState.cellGroups.keySet()) {
Grid.Column, ?>[] columns = new Grid.Column, ?>[group
.size()];
CellState cellState = rowState.cellGroups.get(group);
int i = 0;
for (String columnId : group) {
columns[i] = columnIdToColumn.get(columnId);
i++;
}
// Set state to be the same as first in group.
updateHeaderCellFromState(row.join(columns), cellState);
}
if (rowState.defaultRow) {
getWidget().setDefaultHeaderRow(row);
}
row.setStyleName(rowState.styleName);
}
}
private void updateHeaderCellFromState(HeaderCell cell, CellState cellState) {
switch (cellState.type) {
case TEXT:
cell.setText(cellState.text);
break;
case HTML:
cell.setHtml(cellState.html);
break;
case WIDGET:
ComponentConnector connector = (ComponentConnector) cellState.connector;
cell.setWidget(connector.getWidget());
break;
default:
throw new IllegalStateException("unexpected cell type: "
+ cellState.type);
}
cell.setStyleName(cellState.styleName);
}
private void updateFooterFromState(GridStaticSectionState state) {
getWidget().setFooterVisible(state.visible);
while (getWidget().getFooterRowCount() > 0) {
getWidget().removeFooterRow(0);
}
for (RowState rowState : state.rows) {
FooterRow row = getWidget().appendFooterRow();
for (CellState cellState : rowState.cells) {
CustomGridColumn column = columnIdToColumn
.get(cellState.columnId);
updateFooterCellFromState(row.getCell(column), cellState);
}
for (Set group : rowState.cellGroups.keySet()) {
Grid.Column, ?>[] columns = new Grid.Column, ?>[group
.size()];
CellState cellState = rowState.cellGroups.get(group);
int i = 0;
for (String columnId : group) {
columns[i] = columnIdToColumn.get(columnId);
i++;
}
// Set state to be the same as first in group.
updateFooterCellFromState(row.join(columns), cellState);
}
row.setStyleName(rowState.styleName);
}
}
private void updateFooterCellFromState(FooterCell cell, CellState cellState) {
switch (cellState.type) {
case TEXT:
cell.setText(cellState.text);
break;
case HTML:
cell.setHtml(cellState.html);
break;
case WIDGET:
ComponentConnector connector = (ComponentConnector) cellState.connector;
cell.setWidget(connector.getWidget());
break;
default:
throw new IllegalStateException("unexpected cell type: "
+ cellState.type);
}
cell.setStyleName(cellState.styleName);
}
/**
* Updates a column from a state change event.
*
* @param columnIndex
* The index of the column to update
*/
private void updateColumnFromStateChangeEvent(GridColumnState columnState) {
CustomGridColumn column = columnIdToColumn.get(columnState.id);
columnsUpdatedFromState = true;
updateColumnFromState(column, columnState);
columnsUpdatedFromState = false;
}
/**
* Adds a new column to the grid widget from a state change event
*
* @param columnIndex
* The index of the column, according to how it
*/
private void addColumnFromStateChangeEvent(GridColumnState state) {
@SuppressWarnings("unchecked")
CustomGridColumn column = new CustomGridColumn(state.id,
((AbstractRendererConnector) state.rendererConnector));
columnIdToColumn.put(state.id, column);
/*
* Add column to grid. Reordering is handled as a separate problem.
*/
getWidget().addColumn(column);
columnOrder.add(state.id);
}
/**
* If we have a selection column renderer, we need to offset the index by
* one when referring to the column index in the widget.
*/
private int getWidgetColumnIndex(final int columnIndex) {
Renderer selectionColumnRenderer = getWidget()
.getSelectionModel().getSelectionColumnRenderer();
int widgetColumnIndex = columnIndex;
if (selectionColumnRenderer != null) {
widgetColumnIndex++;
}
return widgetColumnIndex;
}
/**
* Updates the column values from a state
*
* @param column
* The column to update
* @param state
* The state to get the data from
*/
@SuppressWarnings("unchecked")
private static void updateColumnFromState(CustomGridColumn column,
GridColumnState state) {
column.setWidth(state.width);
column.setMinimumWidth(state.minWidth);
column.setMaximumWidth(state.maxWidth);
column.setExpandRatio(state.expandRatio);
assert state.rendererConnector instanceof AbstractRendererConnector : "GridColumnState.rendererConnector is invalid (not subclass of AbstractRendererConnector)";
column.setRenderer((AbstractRendererConnector) state.rendererConnector);
column.setSortable(state.sortable);
column.setHidden(state.hidden);
column.setHidable(state.hidable);
column.setHidingToggleCaption(state.hidingToggleCaption);
column.setEditable(state.editable);
column.setEditorConnector((AbstractFieldConnector) state.editorConnector);
}
/**
* Removes any orphan columns that has been removed from the state from the
* grid
*/
private void purgeRemovedColumns() {
// Get columns still registered in the state
Set columnsInState = new HashSet();
for (GridColumnState columnState : getState().columns) {
columnsInState.add(columnState.id);
}
// Remove column no longer in state
Iterator columnIdIterator = columnIdToColumn.keySet()
.iterator();
while (columnIdIterator.hasNext()) {
String id = columnIdIterator.next();
if (!columnsInState.contains(id)) {
CustomGridColumn column = columnIdToColumn.get(id);
columnIdIterator.remove();
getWidget().removeColumn(column);
columnOrder.remove(id);
}
}
}
public void setDataSource(RpcDataSource dataSource) {
this.dataSource = dataSource;
getWidget().setDataSource(this.dataSource);
}
private void onSelectionModeChange() {
SharedSelectionMode mode = getState().selectionMode;
if (mode == null) {
getLogger().fine("ignored mode change");
return;
}
AbstractRowHandleSelectionModel model = createSelectionModel(mode);
if (selectionModel == null
|| !model.getClass().equals(selectionModel.getClass())) {
selectionModel = model;
getWidget().setSelectionModel(model);
selectedKeys.clear();
}
}
@OnStateChange("hasCellStyleGenerator")
private void onCellStyleGeneratorChange() {
if (getState().hasCellStyleGenerator) {
getWidget().setCellStyleGenerator(new CustomCellStyleGenerator());
} else {
getWidget().setCellStyleGenerator(null);
}
}
@OnStateChange("hasRowStyleGenerator")
private void onRowStyleGeneratorChange() {
if (getState().hasRowStyleGenerator) {
getWidget().setRowStyleGenerator(new CustomRowStyleGenerator());
} else {
getWidget().setRowStyleGenerator(null);
}
}
private void updateSelectionFromState() {
boolean changed = false;
List stateKeys = getState().selectedKeys;
// find new deselections
for (String key : selectedKeys) {
if (!stateKeys.contains(key)) {
changed = true;
deselectByHandle(dataSource.getHandleByKey(key));
}
}
// find new selections
for (String key : stateKeys) {
if (!selectedKeys.contains(key)) {
changed = true;
selectByHandle(dataSource.getHandleByKey(key));
}
}
/*
* A defensive copy in case the collection in the state is mutated
* instead of re-assigned.
*/
selectedKeys = new LinkedHashSet(stateKeys);
/*
* We need to fire this event so that Grid is able to re-render the
* selection changes (if applicable).
*/
if (changed) {
// At least for now there's no way to send the selected and/or
// deselected row data. Some data is only stored as keys
selectionUpdatedFromState = true;
getWidget().fireEvent(
new SelectionEvent(getWidget(),
(List) null, null, false));
}
}
private void onSortStateChange() {
List sortOrder = new ArrayList();
String[] sortColumns = getState().sortColumns;
SortDirection[] sortDirs = getState().sortDirs;
for (int i = 0; i < sortColumns.length; i++) {
sortOrder.add(new SortOrder(columnIdToColumn.get(sortColumns[i]),
sortDirs[i]));
}
getWidget().setSortOrder(sortOrder);
}
private Logger getLogger() {
return Logger.getLogger(getClass().getName());
}
@SuppressWarnings("static-method")
private AbstractRowHandleSelectionModel createSelectionModel(
SharedSelectionMode mode) {
switch (mode) {
case SINGLE:
return new SelectionModelSingle();
case MULTI:
return new SelectionModelMulti();
case NONE:
return new SelectionModelNone();
default:
throw new IllegalStateException("unexpected mode value: " + mode);
}
}
/**
* A workaround method for accessing the protected method
* {@code AbstractRowHandleSelectionModel.selectByHandle}
*/
private native void selectByHandle(RowHandle handle)
/*-{
var model = [email protected] ::selectionModel;
model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle);
}-*/;
/**
* A workaround method for accessing the protected method
* {@code AbstractRowHandleSelectionModel.deselectByHandle}
*/
private native void deselectByHandle(RowHandle handle)
/*-{
var model = [email protected] ::selectionModel;
model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle);
}-*/;
/**
* Gets the row key for a row object.
*
* @param row
* the row object
* @return the key for the given row
*/
public String getRowKey(JsonObject row) {
final Object key = dataSource.getRowKey(row);
assert key instanceof String : "Internal key was not a String but a "
+ key.getClass().getSimpleName() + " (" + key + ")";
return (String) key;
}
/*
* (non-Javadoc)
*
* @see
* com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin.client
* .ComponentConnector)
*/
@Override
public void updateCaption(ComponentConnector connector) {
// TODO Auto-generated method stub
}
@Override
public void onConnectorHierarchyChange(
ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
}
public String getColumnId(Grid.Column, ?> column) {
if (column instanceof CustomGridColumn) {
return ((CustomGridColumn) column).id;
}
return null;
}
@Override
public void layout() {
getWidget().onResize();
}
@Override
public boolean isWorkPending() {
return detailsConnectorFetcher.isWorkPending()
|| lazyDetailsScrollAdjuster.isWorkPending();
}
/**
* Gets the listener used by this connector for tracking when row detail
* visibility changes.
*
* @since 7.5.0
* @return the used details listener
*/
public DetailsListener getDetailsListener() {
return detailsListener;
}
}