package org.activityinfo.ui.client.component.importDialog.mapping;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.client.Command;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.CellPreviewEvent.Handler;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import org.activityinfo.core.shared.importing.model.ColumnAction;
import org.activityinfo.core.shared.importing.model.IgnoreAction;
import org.activityinfo.core.shared.importing.model.ImportModel;
import org.activityinfo.core.shared.importing.source.SourceColumn;
import org.activityinfo.core.shared.importing.source.SourceRow;
import org.activityinfo.core.shared.importing.source.SourceTable;
import org.activityinfo.i18n.shared.I18N;
import org.activityinfo.ui.client.component.importDialog.PageChangedEvent;
import org.activityinfo.ui.client.style.table.DataGridResources;
import org.activityinfo.ui.client.util.GwtUtil;
import java.util.List;
/**
* A DataGrid that shows the original columns in the imported table
* and focuses on helping the user select columns as a whole and map them
* to existing properties.
*/
public class ColumnMappingGrid extends DataGrid<SourceRow> {
public static final int SOURCE_COLUMN_HEADER_ROW = 0;
public static final int MAPPING_HEADER_ROW = 1;
private final ImportModel model;
private final EventBus eventBus;
private SingleSelectionModel<SourceColumn> columnSelectionModel;
private List<SourceColumn> sourceColumns;
private final GridHeaderCell headerCell;
private int lastSelectedColumn = -1;
public ColumnMappingGrid(ImportModel model, SingleSelectionModel<SourceColumn> columnSelectionModel,
EventBus eventBus) {
super(50, DataGridResources.INSTANCE);
ColumnMappingStyles.INSTANCE.ensureInjected();
DataGridResources.INSTANCE.dataGridStyle().ensureInjected();
this.model = model;
this.columnSelectionModel = columnSelectionModel;
this.eventBus = eventBus;
headerCell = new GridHeaderCell(model);
this.addStyleName(ColumnMappingStyles.INSTANCE.grid());
this.setWidth("100%");
this.setHeight("100%");
this.setSelectionModel(new NullRowSelectionModel());
this.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
this.setHeaderBuilder(new GridHeaderBuilder(this));
this.setSkipRowHoverCheck(true);
this.addCellPreviewHandler(new Handler<SourceRow>() {
@Override
public void onCellPreview(CellPreviewEvent<SourceRow> event) {
if (BrowserEvents.CLICK.equals(event.getNativeEvent().getType())) {
SourceColumn sourceColumn = sourceColumns.get(event.getColumn());
ColumnMappingGrid.this.columnSelectionModel.setSelected(sourceColumn, true);
}
}
});
this.columnSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override
public void onSelectionChange(SelectionChangeEvent event) {
Scheduler.get().scheduleDeferred(new Command() {
@Override
public void execute() {
onColumnSelectionChanged();
}
});
}
});
}
private void onColumnSelectionChanged() {
// ensure the column is scrolled into view
int newColumnIndex = columnSelectionModel.getSelectedObject().getIndex();
scrollColumnIntoView(newColumnIndex);
// clear the selection styles from the old column
if (lastSelectedColumn != -1) {
removeHeaderStyleName(lastSelectedColumn, ColumnMappingStyles.INSTANCE.selected());
this.removeColumnStyleName(lastSelectedColumn, ColumnMappingStyles.INSTANCE.selected());
}
// add the bg to the new selection
this.getHeader(newColumnIndex).setHeaderStyleNames(ColumnMappingStyles.INSTANCE.selected());
this.addColumnStyleName(newColumnIndex, ColumnMappingStyles.INSTANCE.selected());
lastSelectedColumn = newColumnIndex;
}
/**
* Updates the column styles to match the column's current binding
*/
public void refreshColumnStyles(int columnIndex) {
// update the column styles
final SourceColumn sourceColumn = model.getSourceColumn(columnIndex);
ColumnAction binding = model.getColumnAction(sourceColumn);
toggleColumnStyle(columnIndex, ColumnMappingStyles.INSTANCE.stateIgnored(), binding != null &&
binding == IgnoreAction.INSTANCE);
toggleColumnStyle(columnIndex, ColumnMappingStyles.INSTANCE.stateBound(), binding != null &&
binding != IgnoreAction.INSTANCE);
toggleColumnStyle(columnIndex, ColumnMappingStyles.INSTANCE.stateUnset(), binding == null);
// update the mapping description
Cell.Context context = new Cell.Context(MAPPING_HEADER_ROW, columnIndex, null);
SafeHtmlBuilder html = new SafeHtmlBuilder();
headerCell.render(context, sourceColumn, html);
getTableHead(MAPPING_HEADER_ROW, columnIndex).setInnerSafeHtml(html.toSafeHtml());
}
private void scrollColumnIntoView(int selectedColumnIndex) {
Element td = this.getRowElement(0).getChild(selectedColumnIndex).cast();
td.scrollIntoView();
}
private void addHeaderStyleName(int columnIndex, String className) {
getTableHead(SOURCE_COLUMN_HEADER_ROW, columnIndex).addClassName(className);
getTableHead(MAPPING_HEADER_ROW, columnIndex).addClassName(className);
}
private void removeHeaderStyleName(int columnIndex, String className) {
getTableHead(SOURCE_COLUMN_HEADER_ROW, columnIndex).removeClassName(className);
getTableHead(MAPPING_HEADER_ROW, columnIndex).removeClassName(className);
}
private Element getTableHead(int rowIndex, int columnIndex) {
final TableRowElement row = getTableHeadElement().getRows().getItem(rowIndex);
return row.getCells().getItem(columnIndex);
}
private void toggleColumnStyle(int index, String className, boolean enabled) {
if (enabled) {
this.addColumnStyleName(index, className);
addHeaderStyleName(index, className);
} else {
this.removeColumnStyleName(index, className);
removeHeaderStyleName(index, className);
}
}
public void refresh() {
while (this.getColumnCount() > 0) {
this.removeColumn(0);
}
sourceColumns = model.getSource().getColumns();
for (SourceColumn sourceColumn : sourceColumns) {
GridColumn gridColumn = new GridColumn(sourceColumn);
GridHeader gridHeader = new GridHeader(sourceColumn, headerCell, columnSelectionModel);
this.addColumn(gridColumn, gridHeader);
this.setColumnWidth(gridColumn, GwtUtil.columnWidthInEm(sourceColumn.getHeader()), com.google.gwt.dom.client.Style.Unit.EM);
}
this.redrawHeaders();
// show already parsed data (it can be only part of it, not all)
this.setRowData(model.getSource().getRows());
// update table if not all rows are parsed
if (!model.getSource().parsedAllRows()) {
// give some time to switch the page
Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
refreshWithNewlyParsedRows();
return false;
}
}, 2000); // wait 2 seconds
}
}
private void refreshWithNewlyParsedRows() {
eventBus.fireEvent(new PageChangedEvent(false, I18N.CONSTANTS.parsingRows()));
Scheduler.get().scheduleFixedPeriod(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
SourceTable sourceTable = model.getSource();
if (!sourceTable.parsedAllRows()) {
sourceTable.parseNextRows(50);
ColumnMappingGrid.this.setRowData(model.getSource().getRows());
ColumnMappingGrid.this.getRowElement(ColumnMappingGrid.this.getRowCount() - 1).scrollIntoView();
} else {
ColumnMappingGrid.this.scrollColumnIntoView(0);
eventBus.fireEvent(new PageChangedEvent(true, ""));
return false;
}
return true;
}
}, 1);
}
}