blob: 5058fa1ca556de7fece4e5fa060b9061485ac2ab [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.google.gwt.sample.expenses.client.place;
import com.google.gwt.activity.shared.Activity;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceChangeEvent;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyChange;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.Receiver;
import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.WriteOperation;
import com.google.gwt.sample.expenses.client.place.ProxyPlace.Operation;
import com.google.gwt.user.cellview.client.AbstractHasData;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.RangeChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Abstract activity for displaying a list of {@link EntityProxy}. These
* activities are not re-usable. Once they are stopped, they cannot be
* restarted.
* <p>
* Subclasses must:
*
* <ul>
* <li>provide a {@link ProxyListView}
* <li>implement method to request a full count
* <li>implement method to find a range of entities
* <li>respond to "show details" commands
* </ul>
* <p>
* Only the properties required by the view will be requested.
*
* @param <P> the type of {@link EntityProxy} listed
*/
public abstract class AbstractProxyListActivity<P extends EntityProxy>
implements Activity, ProxyListView.Delegate<P> {
/**
* This mapping allows us to update individual rows as records change.
*/
private final Map<EntityProxyId<P>, Integer> idToRow = new HashMap<EntityProxyId<P>, Integer>();
private final Map<EntityProxyId<P>, P> idToProxy = new HashMap<EntityProxyId<P>, P>();
private final PlaceController placeController;
private final SingleSelectionModel<P> selectionModel;
private final Class<P> proxyClass;
private HandlerRegistration rangeChangeHandler;
private ProxyListView<P> view;
private AcceptsOneWidget display;
public AbstractProxyListActivity(PlaceController placeController,
ProxyListView<P> view, Class<P> proxyType) {
this.view = view;
this.placeController = placeController;
this.proxyClass = proxyType;
view.setDelegate(this);
final HasData<P> hasData = view.asHasData();
rangeChangeHandler = hasData.addRangeChangeHandler(new RangeChangeEvent.Handler() {
public void onRangeChange(RangeChangeEvent event) {
AbstractProxyListActivity.this.onRangeChanged(hasData);
}
});
// Inherit the view's key provider
ProvidesKey<P> keyProvider = ((AbstractHasData<P>) hasData).getKeyProvider();
selectionModel = new SingleSelectionModel<P>(keyProvider);
hasData.setSelectionModel(selectionModel);
selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
public void onSelectionChange(SelectionChangeEvent event) {
P selectedObject = selectionModel.getSelectedObject();
if (selectedObject != null) {
showDetails(selectedObject);
}
}
});
}
public void createClicked() {
placeController.goTo(new ProxyPlace(proxyClass));
}
public ProxyListView<P> getView() {
return view;
}
public String mayStop() {
return null;
}
public void onCancel() {
onStop();
}
/**
* Called by the table as it needs data.
*/
public void onRangeChanged(HasData<P> listView) {
final Range range = listView.getVisibleRange();
final Receiver<List<P>> callback = new Receiver<List<P>>() {
@Override
public void onSuccess(List<P> values) {
if (view == null) {
// This activity is dead
return;
}
idToRow.clear();
idToProxy.clear();
for (int i = 0, row = range.getStart(); i < values.size(); i++, row++) {
P proxy = values.get(i);
@SuppressWarnings("unchecked")
// Why is this cast needed?
EntityProxyId<P> proxyId = (EntityProxyId<P>) proxy.stableId();
idToRow.put(proxyId, row);
idToProxy.put(proxyId, proxy);
}
getView().asHasData().setRowData(range.getStart(), values);
if (display != null) {
display.setWidget(getView());
}
}
};
fireRangeRequest(range, callback);
}
public void onStop() {
view.setDelegate(null);
view = null;
rangeChangeHandler.removeHandler();
rangeChangeHandler = null;
}
/**
* Select the given record, or clear the selection if called with null or an
* id we don't know.
*/
public void select(EntityProxyId<P> proxyId) {
/*
* The selectionModel will not flash if we put it back to the same state it
* is already in, so we can keep this code simple.
*/
// Clear the selection
P selected = selectionModel.getSelectedObject();
if (selected != null) {
selectionModel.setSelected(selected, false);
}
// Select the new proxy, if it's relevant
if (proxyId != null) {
P selectMe = idToProxy.get(proxyId);
selectionModel.setSelected(selectMe, true);
}
}
public void start(AcceptsOneWidget display, EventBus eventBus) {
view.setDelegate(this);
EntityProxyChange.registerForProxyType(eventBus, proxyClass,
new EntityProxyChange.Handler<P>() {
public void onProxyChange(EntityProxyChange<P> event) {
update(event.getWriteOperation(), event.getProxyId());
}
});
eventBus.addHandler(PlaceChangeEvent.TYPE, new PlaceChangeEvent.Handler() {
public void onPlaceChange(PlaceChangeEvent event) {
updateSelection(event.getNewPlace());
}
});
this.display = display;
init();
updateSelection(placeController.getWhere());
}
public void update(WriteOperation writeOperation, EntityProxyId<P> proxyId) {
switch (writeOperation) {
case UPDATE:
update(proxyId);
break;
case DELETE:
init();
break;
case PERSIST:
/*
* On create, we presume the new record is at the end of the list, so
* fetch the last page of items.
*/
getLastPage();
break;
}
}
protected abstract Request<List<P>> createRangeRequest(Range range);
protected abstract void fireCountRequest(Receiver<Long> callback);
/**
* Called when the user chooses a record to view. This default implementation
* sends the {@link PlaceController} to an appropriate {@link ProxyPlace}.
*
* @param record the chosen record
*/
protected void showDetails(P record) {
placeController.goTo(new ProxyPlace(record.stableId(), Operation.DETAILS));
}
@SuppressWarnings("unchecked")
private EntityProxyId<P> cast(ProxyPlace proxyPlace) {
return (EntityProxyId<P>) proxyPlace.getProxyId();
}
private void fireRangeRequest(final Range range,
final Receiver<List<P>> callback) {
createRangeRequest(range).with(getView().getPaths()).fire(callback);
}
private void getLastPage() {
fireCountRequest(new Receiver<Long>() {
@Override
public void onSuccess(Long response) {
if (view == null) {
// This activity is dead
return;
}
HasData<P> table = getView().asHasData();
int rows = response.intValue();
table.setRowCount(rows, true);
if (rows > 0) {
int pageSize = table.getVisibleRange().getLength();
int remnant = rows % pageSize;
if (remnant == 0) {
table.setVisibleRange(rows - pageSize, pageSize);
} else {
table.setVisibleRange(rows - remnant, pageSize);
}
}
onRangeChanged(table);
}
});
}
private void init() {
fireCountRequest(new Receiver<Long>() {
@Override
public void onSuccess(Long response) {
if (view == null) {
// This activity is dead
return;
}
getView().asHasData().setRowCount(response.intValue(), true);
onRangeChanged(view.asHasData());
}
});
}
private void update(EntityProxyId<P> proxyId) {
final Integer row = idToRow.get(proxyId);
if (row == null) {
return;
}
fireRangeRequest(new Range(row, 1), new Receiver<List<P>>() {
@Override
public void onSuccess(List<P> response) {
getView().asHasData().setRowData(row,
Collections.singletonList(response.get(0)));
}
});
}
private void updateSelection(Place newPlace) {
if (newPlace instanceof ProxyPlace) {
ProxyPlace proxyPlace = (ProxyPlace) newPlace;
if (proxyPlace.getOperation() != Operation.CREATE
&& proxyPlace.getProxyClass().equals(proxyClass)) {
select(cast(proxyPlace));
return;
}
}
select(null);
}
}