/* * Copyright 2000-2016 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.v7.client.connectors; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.vaadin.client.ServerConnector; import com.vaadin.client.data.AbstractRemoteDataSource; import com.vaadin.client.extensions.AbstractExtensionConnector; import com.vaadin.shared.Range; import com.vaadin.shared.data.DataProviderRpc; import com.vaadin.shared.data.DataRequestRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.v7.shared.ui.grid.GridState; import elemental.json.Json; import elemental.json.JsonArray; import elemental.json.JsonObject; /** * Connects a Vaadin server-side container data source to a Grid. This is * currently implemented as an Extension hardcoded to support a specific * connector type. This will be changed once framework support for something * more flexible has been implemented. * * @since 7.4 * @author Vaadin Ltd */ @Connect(com.vaadin.v7.server.communication.data.RpcDataProviderExtension.class) public class RpcDataSourceConnector extends AbstractExtensionConnector { /** * A callback interface to let {@link GridConnector} know that detail * visibilities might have changed. * * @since 7.5.0 * @author Vaadin Ltd */ interface DetailsListener { /** * A request to verify (and correct) the visibility for a row, given * updated metadata. * * @param rowIndex * the index of the row that should be checked * @param row * the row object to check visibility for * @see GridState#JSONKEY_DETAILS_VISIBLE */ void reapplyDetailsVisibility(int rowIndex, JsonObject row); } public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> { protected RpcDataSource() { registerRpc(DataProviderRpc.class, new DataProviderRpc() { @Override public void setRowData(int firstRow, JsonArray rowArray) { ArrayList<JsonObject> rows = new ArrayList<JsonObject>( rowArray.length()); for (int i = 0; i < rowArray.length(); i++) { JsonObject rowObject = rowArray.getObject(i); rows.add(rowObject); } RpcDataSource.this.setRowData(firstRow, rows); } @Override public void removeRowData(int firstRow, int count) { RpcDataSource.this.removeRowData(firstRow, count); } @Override public void insertRowData(int firstRow, int count) { RpcDataSource.this.insertRowData(firstRow, count); } @Override public void resetDataAndSize(int size) { RpcDataSource.this.resetDataAndSize(size); } @Override public void updateRowData(JsonArray rowArray) { for (int i = 0; i < rowArray.length(); ++i) { RpcDataSource.this.updateRowData(rowArray.getObject(i)); } } }); } private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class); private DetailsListener detailsListener; private JsonArray droppedRowKeys = Json.createArray(); @Override protected void requestRows(int firstRowIndex, int numberOfRows, RequestRowsCallback<JsonObject> callback) { if (droppedRowKeys.length() > 0) { rpcProxy.dropRows(droppedRowKeys); droppedRowKeys = Json.createArray(); } /* * If you're looking at this code because you want to learn how to * use AbstactRemoteDataSource, please look somewhere else instead. * * We're not doing things in the conventional way with the callback * here since Vaadin doesn't directly support RPC with return * values. We're instead asking the server to push us some data, and * when we receive pushed data, we just push it along to the * underlying cache in the same way no matter if it was a genuine * push or just a result of us requesting rows. */ Range cached = getCachedRange(); rpcProxy.requestRows(firstRowIndex, numberOfRows, cached.getStart(), cached.length()); /* * Show the progress indicator if there is a pending data request * and some of the visible rows are being requested. The RPC in * itself will not trigger the indicator since it might just fetch * some rows in the background to fill the cache. * * The indicator will be hidden by the framework when the response * is received (unless another request is already on its way at that * point). */ if (getRequestedAvailability().intersects( Range.withLength(firstRowIndex, numberOfRows))) { getConnection().getLoadingIndicator().ensureTriggered(); } } @Override public void ensureAvailability(int firstRowIndex, int numberOfRows) { super.ensureAvailability(firstRowIndex, numberOfRows); /* * We trigger the indicator already at this point since the actual * RPC will not be sent right away when waiting for the response to * a previous request. * * Only triggering here would not be enough since the check that * sets isWaitingForData is deferred. We don't want to trigger the * loading indicator here if we don't know that there is actually a * request going on since some other bug might then cause the * loading indicator to not be hidden. */ if (isWaitingForData() && !Range.withLength(firstRowIndex, numberOfRows) .isSubsetOf(getCachedRange())) { getConnection().getLoadingIndicator().ensureTriggered(); } } @Override public String getRowKey(JsonObject row) { if (row.hasKey(GridState.JSONKEY_ROWKEY)) { return row.getString(GridState.JSONKEY_ROWKEY); } else { return null; } } public RowHandle<JsonObject> getHandleByKey(Object key) { JsonObject row = Json.createObject(); row.put(GridState.JSONKEY_ROWKEY, (String) key); return new RowHandleImpl(row, key); } @Override protected void unpinHandle(RowHandleImpl handle) { // Row data is no longer available after it has been unpinned. String key = getRowKey(handle.getRow()); super.unpinHandle(handle); if (!handle.isPinned()) { if (indexOfKey(key) == -1) { // Row out of view has been unpinned. drop it droppedRowKeys.set(droppedRowKeys.length(), key); } } } void setDetailsListener(DetailsListener detailsListener) { this.detailsListener = detailsListener; } @Override protected void setRowData(int firstRowIndex, List<JsonObject> rowData) { super.setRowData(firstRowIndex, rowData); /* * Intercepting details information from the data source, rerouting * them back to the GridConnector (as a details listener) */ for (int i = 0; i < rowData.size(); i++) { detailsListener.reapplyDetailsVisibility(firstRowIndex + i, rowData.get(i)); } } /** * Updates row data based on row key. * * @since 7.6 * @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, JsonObject row) { if (!isPinned(row)) { droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row)); } } @Override protected boolean canFetchData() { return isEnabled(); } } private final RpcDataSource dataSource = new RpcDataSource(); @Override protected void extend(ServerConnector target) { GridConnector gridConnector = (GridConnector) target; dataSource.setDetailsListener(gridConnector.getDetailsListener()); gridConnector.setDataSource(dataSource); } }