package org.yaac.client.ui;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.yaac.client.YaacCallback;
import org.yaac.client.activity.BrowserActivity;
import org.yaac.client.util.ExceptionManager;
import org.yaac.shared.crud.MetaNamespace;
import org.yaac.shared.editor.EntityInfo;
import org.yaac.shared.util.StringUtils;
import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.Range;
/**
* @author Max Zhu (thebbsky@gmail.com)
*
*/
public class BrowserViewImpl extends Composite implements BrowserView {
private static BrowserViewImplUiBinder uiBinder = GWT.create(BrowserViewImplUiBinder.class);
@UiTemplate("BrowserView.ui.xml")
interface BrowserViewImplUiBinder extends UiBinder<Widget, BrowserViewImpl> {
}
@UiField
ListBox namespaceListBox;
@UiField
ListBox kindListBox;
@UiField
TextBox filtersTxt;
// internal status for filters text
private boolean waitingForFirstClick;
@UiField(provided=true)
SimplePager pager;
@UiField(provided=true)
CellTable<EntityInfo> entityTable;
private BrowserActivity listener;
private List<MetaNamespace> namespaces;
// make sure metadata will only be loaded once by activity
private boolean isMetaDataInit;
private final ExceptionManager exceptionManager;
@Inject
BrowserViewImpl(ExceptionManager exceptionManager) {
this.exceptionManager = exceptionManager;
// init entity table
entityTable = new CellTable<EntityInfo>();
entityTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
// Create a Pager to control the table.
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
pager.setDisplay(entityTable);
initWidget(uiBinder.createAndBindUi(this));
// disable them at the begining
namespaceListBox.setEnabled(false);
kindListBox.setEnabled(false);
filtersTxt.setEnabled(false);
this.waitingForFirstClick = true;
this.isMetaDataInit = false;
}
@UiHandler("filtersTxt")
void onFiltersTxtClick(ClickEvent event) {
if (waitingForFirstClick) {
filtersTxt.setText("");
waitingForFirstClick = false;
}
}
@UiHandler("filtersTxt")
void onFiltersTxtKeyPress(KeyPressEvent event) {
char key = event.getCharCode();
if (KeyCodes.KEY_ENTER == key) {
refreshDataGrid();
}
}
@Override
public void setPresenter(BrowserActivity listener) {
this.listener = listener;
}
@Override
public void onMetadataReady(List<MetaNamespace> namespaces) {
this.isMetaDataInit = true;
this.namespaces = namespaces;
this.namespaceListBox.setEnabled(true);
this.kindListBox.setEnabled(true);
this.filtersTxt.setEnabled(true);
this.namespaceListBox.clear();
for (MetaNamespace namespace : namespaces) {
this.namespaceListBox.addItem(namespace.getNamespaceName());
}
this.refreshKindListBox();
}
private void refreshKindListBox() {
String selectedNamespace = namespaceListBox.getValue(namespaceListBox.getSelectedIndex());
for (MetaNamespace namespace : namespaces) {
// we want to make null == empty string here
// reason it list box will convert null to emtpy string automatically
if ((isNullOrEmpty(namespace.getNamespaceName()) && isNullOrEmpty(selectedNamespace))
|| namespace.getNamespaceName().equals(selectedNamespace)) {
this.kindListBox.clear();
for (String kindName : namespace.getKindsMap().keySet()) {
this.kindListBox.addItem(kindName);
}
break;
}
}
this.refreshDataGrid();
}
private void refreshDataGrid() {
String selectedKindName = kindListBox.getValue(kindListBox.getSelectedIndex());
String filters = waitingForFirstClick ? null : filtersTxt.getText().trim();
this.updateEntityTable(selectedKindName, filters);
}
@UiHandler("namespaceListBox")
void onNamespaceListBoxChange(ChangeEvent event) {
this.refreshKindListBox();
}
@UiHandler("kindListBox")
void onKindListBoxChange(ChangeEvent event) {
this.filtersTxt.setText(""); // clear filters
this.refreshDataGrid();
}
private BrowserDataProvider currDataProvider;
/**
* no need to reload when kind is not changed
*/
private String currKind;
private String currFilter;
/**
* current column map, to make sure same column will not be duplicated
*/
private Map<String, Column<EntityInfo, SafeHtml>> currColMap;
private void updateEntityTable(String kind, String filter) {
if (!StringUtils.sameStr(kind, currKind) || !StringUtils.sameStr(filter, currFilter)) {
currKind = kind;
currFilter = filter;
currColMap = new HashMap<String, Column<EntityInfo, SafeHtml>>();
// step 1 : prepare tables based on selected properties
// remove all existing columns
while (entityTable.getColumnCount() > 0) {
entityTable.removeColumn(0);
}
// only key now, all columns are dynamic
entityTable.addColumn(new Column<EntityInfo, SafeHtml>(new SafeHtmlCell()) {
@Override
public SafeHtml getValue(EntityInfo entity) {
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant(entity.getKey().asHtml());
return sb.toSafeHtml();
}
}, "ID/Name"); // TODO : i18n
// reset to first page
pager.firstPage();
// step 2 : load table
// replace current data provider with new one
if (currDataProvider != null) {
entityTable.setRowCount(0, false);
currDataProvider.removeDataDisplay(entityTable);
}
currDataProvider = new BrowserDataProvider(currKind, currFilter);
currDataProvider.addDataDisplay(entityTable);
}
}
/**
* check all entities, make sure no column(s) is missing
*
* @param vals
*/
private void ensureNewColumns(List<EntityInfo> vals) {
for (EntityInfo entity : vals) {
for (final String propertyName : entity.getPropertisMap().keySet()) {
if (!currColMap.containsKey(propertyName)) {
Column<EntityInfo, SafeHtml> col = new Column<EntityInfo, SafeHtml>(new SafeHtmlCell()) {
@Override
public SafeHtml getValue(EntityInfo entity) {
String html = entity.propertyAsHtml(propertyName);
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant(html);
return sb.toSafeHtml();
}
};
entityTable.addColumn(col, propertyName);
currColMap.put(propertyName, col);
}
}
}
}
/**
* @author Max Zhu (thebbsky@gmail.com)
*
*/
private class BrowserDataProvider extends AsyncDataProvider<EntityInfo> {
private final String kindName;
private final String filter;
private final List<EntityInfo> cache;
BrowserDataProvider(String kindName, String filter) {
super();
this.kindName = kindName;
this.filter = filter;
// it's less likely that user will browse more than 1000 records
this.cache = new ArrayList<EntityInfo>(1000);
}
@Override
protected void onRangeChanged(HasData<EntityInfo> display) {
Range range = display.getVisibleRange();
final int start = range.getStart();
final int length = range.getLength();
if (start + length < cache.size()) {
List<EntityInfo> vals = cache.subList(start, start + length);
updateRowData(start, vals);
} else {
listener.loadEntities(kindName, filter, start, length,
new YaacCallback<List<EntityInfo>>() {
@Override
public void onSuccess(List<EntityInfo> vals) {
if (vals.isEmpty()) {
Window.alert("No result returned");
}
cache.addAll(start, vals);
ensureNewColumns(vals);
updateRowData(start, vals);
}
@Override
public void onFailure(Throwable caught) {
exceptionManager.handle(caught);
}
});
}
}
}
@Override
public boolean isMetaDataInit() {
return this.isMetaDataInit;
}
}