package org.yaac.client.widget.egql;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.transform;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.yaac.client.i18n.ErrorMessages;
import org.yaac.client.util.DOMUtil;
import org.yaac.client.widget.SortHandler;
import org.yaac.client.widget.SortableColumn;
import org.yaac.shared.egql.ResultCell;
import org.yaac.shared.egql.ResultStatus;
import org.yaac.shared.property.PropertyInfo;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.event.dom.client.ClickEvent;
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.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
/**
* @author Max Zhu (thebbsky@gmail.com)
*
*/
public class EGQLResultPanel extends Composite {
private static QueryResultPanelUiBinder uiBinder = GWT.create(QueryResultPanelUiBinder.class);
interface QueryResultPanelUiBinder extends UiBinder<Widget, EGQLResultPanel> {
}
@UiField
Button closeBtn;
@UiField
DivElement rawStmtDiv;
@UiField
TextArea statusTxt;
@UiField(provided = true)
CellTable<List<ResultCell>> resultTable;
@UiField
ScrollPanel resultScroll;
private ListDataProvider<List<ResultCell>> dataProvider;
private SortHandler<List<ResultCell>> sortHandler;
/**
* column map, to make sure same column will not be duplicated
*/
private Map<String, SortableColumn<List<ResultCell>, SafeHtml>> colMap;
private static ErrorMessages errMsgs = GWT.create(ErrorMessages.class);
/**
*
*/
public EGQLResultPanel() {
// init result table
resultTable = new CellTable<List<ResultCell>>();
resultTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
resultTable.setVisibleRange(0, Integer.MAX_VALUE);
// init column map
colMap = new HashMap<String, SortableColumn<List<ResultCell>, SafeHtml>>();
// init sort handler
sortHandler = new SortHandler<List<ResultCell>>();
resultTable.addColumnSortHandler(sortHandler);
initWidget(uiBinder.createAndBindUi(this));
// set status text to read only
statusTxt.setReadOnly(true);
}
public EGQLResultPanel withStatement(String rawStmt) {
// raw statement
rawStmtDiv.setInnerText(rawStmt);
rawStmtDiv.setTitle(rawStmt);
return this;
}
/**
* @param data
* @return
*/
public EGQLResultPanel appendData(List<List<ResultCell>> data) {
// it is possible when executing group by quries
if (data == null || data.isEmpty()) {
return this;
}
// dynamic adding columns, any new incoming data may potentially contain not-exist column(s)
ensureColumns(data);
// lazy initialize data provider,
// to make sure there is a waiting icon before first piece of data arrived
ensureDataProvider();
this.dataProvider.getList().addAll(data);
this.sortHandler.refresh();
this.resultScroll.scrollToBottom();
return this;
}
private void ensureDataProvider () {
if (this.dataProvider == null) {
dataProvider = new ListDataProvider<List<ResultCell>>();
dataProvider.addDataDisplay(resultTable);
sortHandler.setDataProvider(dataProvider);
}
}
private void ensureColumns(List<List<ResultCell>> data) {
for (List<ResultCell> row : data) {
for (final ResultCell cell : row) {
if (!colMap.containsKey(cell.getTitle())) {
SortableColumn<List<ResultCell>, SafeHtml> column =
new SortableColumn<List<ResultCell>, SafeHtml>(new SafeHtmlCell()) {
@Override
public SafeHtml getValue(List<ResultCell> cells) {
ResultCell c = lookupCell(cell.getTitle(), cells);
SafeHtmlBuilder sb = new SafeHtmlBuilder();
if (c == null) { // not necessary always have this column
sb.appendHtmlConstant("<br/>");
} else {
PropertyInfo property = PropertyInfo.Builder.fromResultCell(c);
// populate warnings in tooltip
List<String> warnings = property.getWarnings();
if (warnings != null && !warnings.isEmpty()) {
List<String> warningMsgs = transform(warnings, new Function<String, String>(){
@Override
public String apply(String errorCode) {
return errMsgs.getString(errorCode);
}
});
sb.appendHtmlConstant(
"<div title='" + Joiner.on("<br/>").join(warningMsgs) + "' style='background-color: yellow;'>" +
property.asHtml() + "</div>");
} else {
sb.appendHtmlConstant(property.asHtml());
}
}
return sb.toSafeHtml();
}
};
resultTable.addColumn(column, cell.getTitle());
colMap.put(cell.getTitle(), column);
sortHandler.setComparator(column, new Comparator<List<ResultCell>>() {
@Override
public int compare(List<ResultCell> cell1s, List<ResultCell> cell2s) {
// dynamic columns, both c1 and c2 can be null
ResultCell c1 = lookupCell(cell.getTitle(), cell1s);
ResultCell c2 = lookupCell(cell.getTitle(), cell2s);
if (c1 == null) {
if (c2 == null) {
return 0;
} else {
return -1;
}
} else {
if (c2 == null) {
return 1;
} else {
PropertyInfo prop1 = PropertyInfo.Builder.fromResultCell(c1);
PropertyInfo prop2 = PropertyInfo.Builder.fromResultCell(c2);
return prop1.compareTo(prop2);
}
}
}
});
}
}
}
}
// TODO : make it faster by using map, CAUTION: column orders will get lost by using map
private ResultCell lookupCell(String title, List<ResultCell> cells) {
for (ResultCell cell : cells) {
if (cell.getTitle().equals(title)) {
return cell;
}
}
return null;
}
/**
* @param msg
* @return
*/
private EGQLResultPanel status(String msg) {
String curr = statusTxt.getValue();
String newStr = isNullOrEmpty(curr) ? msg : curr + "\n" + msg;
statusTxt.setText(newStr);
DOMUtil.scrollToBottom(statusTxt.getElement());
return this;
}
public void status(ResultStatus status) {
if (status == null)
return;
status(status.toString());
if (status == ResultStatus.FINISHED) {
// just to remove the waiting icon
// in case there are no result returned at all
this.ensureDataProvider();
}
}
/**
* @param event
*/
// TODO : kill backend statements as well
@UiHandler("closeBtn")
void onCloseBtnClick(ClickEvent event) {
this.removeFromParent();
}
}