Implemented column addition and removal features.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@41 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-02-04 23:38:40 +00:00
parent bacb71ab6d
commit f8e15798e2
14 changed files with 371 additions and 49 deletions

View File

@ -15,8 +15,10 @@ import org.json.JSONObject;
import org.json.JSONTokener; import org.json.JSONTokener;
import com.metaweb.gridworks.commands.Command; import com.metaweb.gridworks.commands.Command;
import com.metaweb.gridworks.commands.edit.AddColumnCommand;
import com.metaweb.gridworks.commands.edit.CreateProjectFromUploadCommand; import com.metaweb.gridworks.commands.edit.CreateProjectFromUploadCommand;
import com.metaweb.gridworks.commands.edit.DoTextTransformCommand; import com.metaweb.gridworks.commands.edit.DoTextTransformCommand;
import com.metaweb.gridworks.commands.edit.RemoveColumnCommand;
import com.metaweb.gridworks.commands.edit.UndoRedoCommand; import com.metaweb.gridworks.commands.edit.UndoRedoCommand;
import com.metaweb.gridworks.commands.info.ComputeFacetsCommand; import com.metaweb.gridworks.commands.info.ComputeFacetsCommand;
import com.metaweb.gridworks.commands.info.GetAllProjectMetadataCommand; import com.metaweb.gridworks.commands.info.GetAllProjectMetadataCommand;
@ -51,6 +53,9 @@ public class GridworksServlet extends HttpServlet {
_commands.put("compute-facets", new ComputeFacetsCommand()); _commands.put("compute-facets", new ComputeFacetsCommand());
_commands.put("do-text-transform", new DoTextTransformCommand()); _commands.put("do-text-transform", new DoTextTransformCommand());
_commands.put("add-column", new AddColumnCommand());
_commands.put("remove-column", new RemoveColumnCommand());
_commands.put("reconcile", new ReconcileCommand()); _commands.put("reconcile", new ReconcileCommand());
_commands.put("approve-reconcile", new ApproveReconcileCommand()); _commands.put("approve-reconcile", new ApproveReconcileCommand());
_commands.put("approve-new-reconcile", new ApproveNewReconcileCommand()); _commands.put("approve-new-reconcile", new ApproveNewReconcileCommand());

View File

@ -0,0 +1,30 @@
package com.metaweb.gridworks.commands.edit;
import javax.servlet.http.HttpServletRequest;
import org.json.JSONObject;
import com.metaweb.gridworks.commands.EngineDependentCommand;
import com.metaweb.gridworks.model.AbstractOperation;
import com.metaweb.gridworks.model.operations.ColumnAdditionOperation;
public class AddColumnCommand extends EngineDependentCommand {
@Override
protected AbstractOperation createOperation(HttpServletRequest request,
JSONObject engineConfig) throws Exception {
int baseCellIndex = Integer.parseInt(request.getParameter("baseCellIndex"));
String expression = request.getParameter("expression");
String headerLabel = request.getParameter("headerLabel");
int columnInsertIndex = Integer.parseInt(request.getParameter("columnInsertIndex"));
return new ColumnAdditionOperation(
engineConfig,
baseCellIndex,
expression,
headerLabel,
columnInsertIndex
);
}
}

View File

@ -0,0 +1,37 @@
package com.metaweb.gridworks.commands.edit;
import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.metaweb.gridworks.commands.Command;
import com.metaweb.gridworks.model.AbstractOperation;
import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.operations.ColumnRemovalOperation;
import com.metaweb.gridworks.process.Process;
public class RemoveColumnCommand extends Command {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
Project project = getProject(request);
int columnRemovalIndex = Integer.parseInt(request.getParameter("columnRemovalIndex"));
AbstractOperation op = new ColumnRemovalOperation(columnRemovalIndex);
Process process = op.createProcess(project, new Properties());
boolean done = project.processManager.queueProcess(process);
respond(response, "{ \"code\" : " + (done ? "\"ok\"" : "\"pending\"") + " }");
} catch (Exception e) {
respondException(response, e);
}
}
}

View File

@ -9,7 +9,12 @@ import com.metaweb.gridworks.model.Row;
public class ExpressionUtils { public class ExpressionUtils {
static public Properties createBindings(Project project) { static public Properties createBindings(Project project) {
Properties bindings = new Properties(); Properties bindings = new Properties();
bindings.put("true", true);
bindings.put("false", false);
bindings.put("project", project); bindings.put("project", project);
return bindings; return bindings;
} }

View File

@ -37,6 +37,17 @@ public class Row implements Serializable, HasFields, Jsonizable {
return null; return null;
} }
public void setCell(int cellIndex, Cell cell) {
if (cellIndex < cells.size()) {
cells.set(cellIndex, cell);
} else {
while (cellIndex > cells.size()) {
cells.add(null);
}
cells.add(cell);
}
}
public class Cells implements HasFields { public class Cells implements HasFields {
private Cells() {}; private Cells() {};
@ -62,7 +73,11 @@ public class Row implements Serializable, HasFields, Jsonizable {
writer.key("cells"); writer.array(); writer.key("cells"); writer.array();
for (Cell cell : cells) { for (Cell cell : cells) {
if (cell != null) {
cell.write(writer, options); cell.write(writer, options);
} else {
writer.value(null);
}
} }
writer.endArray(); writer.endArray();

View File

@ -4,6 +4,7 @@ import java.util.List;
import com.metaweb.gridworks.model.Column; import com.metaweb.gridworks.model.Column;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row;
public class ColumnAdditionChange extends ColumnChange { public class ColumnAdditionChange extends ColumnChange {
private static final long serialVersionUID = -3938837464064526052L; private static final long serialVersionUID = -3938837464064526052L;
@ -28,9 +29,12 @@ public class ColumnAdditionChange extends ColumnChange {
Column column = new Column(_newCellIndex, _headerLabel); Column column = new Column(_newCellIndex, _headerLabel);
project.columnModel.columns.add(_columnIndex, column); project.columnModel.columns.add(_columnIndex, column);
try {
for (CellAtRow cell : _newCells) { for (CellAtRow cell : _newCells) {
project.rows.get(cell.row).cells.set(_newCellIndex, cell.cell); project.rows.get(cell.row).setCell(_newCellIndex, cell.cell);
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
} }
@ -39,7 +43,8 @@ public class ColumnAdditionChange extends ColumnChange {
public void revert(Project project) { public void revert(Project project) {
synchronized (project) { synchronized (project) {
for (CellAtRow cell : _newCells) { for (CellAtRow cell : _newCells) {
project.rows.get(cell.row).cells.remove(_newCellIndex); Row row = project.rows.get(cell.row);
row.setCell(_newCellIndex, null);
} }
project.columnModel.columns.remove(_columnIndex); project.columnModel.columns.remove(_columnIndex);

View File

@ -31,6 +31,8 @@ public class ColumnRemovalChange extends ColumnChange {
oldCell = row.cells.get(cellIndex); oldCell = row.cells.get(cellIndex);
} }
_oldCells[i] = new CellAtRow(i, oldCell); _oldCells[i] = new CellAtRow(i, oldCell);
row.setCell(cellIndex, null);
} }
} }
} }

View File

@ -1,19 +0,0 @@
package com.metaweb.gridworks.model.changes;
import com.metaweb.gridworks.model.Project;
public class ColumnSplitChange extends ColumnChange {
@Override
public void apply(Project project) {
// TODO Auto-generated method stub
}
@Override
public void revert(Project project) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,126 @@
package com.metaweb.gridworks.model.operations;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;
import com.metaweb.gridworks.browsing.Engine;
import com.metaweb.gridworks.browsing.FilteredRows;
import com.metaweb.gridworks.browsing.RowVisitor;
import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.ExpressionUtils;
import com.metaweb.gridworks.expr.Parser;
import com.metaweb.gridworks.history.Change;
import com.metaweb.gridworks.history.HistoryEntry;
import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Column;
import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row;
import com.metaweb.gridworks.model.changes.CellAtRow;
import com.metaweb.gridworks.model.changes.ColumnAdditionChange;
import com.metaweb.gridworks.process.Process;
import com.metaweb.gridworks.process.QuickHistoryEntryProcess;
public class ColumnAdditionOperation extends EngineDependentOperation {
private static final long serialVersionUID = -5672677479629932356L;
final protected int _baseCellIndex;
final protected String _expression;
final protected String _headerLabel;
final protected int _columnInsertIndex;
public ColumnAdditionOperation(
JSONObject engineConfig,
int baseCellIndex,
String expression,
String headerLabel,
int columnInsertIndex
) {
super(engineConfig);
_baseCellIndex = baseCellIndex;
_expression = expression;
_headerLabel = headerLabel;
_columnInsertIndex = columnInsertIndex;
}
@Override
public Process createProcess(Project project, Properties options)
throws Exception {
Engine engine = createEngine(project);
Column column = project.columnModel.getColumnByCellIndex(_baseCellIndex);
if (column == null) {
throw new Exception("No column corresponding to cell index " + _baseCellIndex);
}
List<CellAtRow> cellsAtRows = new ArrayList<CellAtRow>(project.rows.size());
FilteredRows filteredRows = engine.getAllFilteredRows();
filteredRows.accept(project, createRowVisitor(project, cellsAtRows));
String description = createDescription(column, cellsAtRows);
Change change = new ColumnAdditionChange(_headerLabel, _columnInsertIndex, cellsAtRows);
HistoryEntry historyEntry = new HistoryEntry(
project, description, this, change);
return new QuickHistoryEntryProcess(project, historyEntry);
}
@Override
public void write(JSONWriter writer, Properties options)
throws JSONException {
// TODO Auto-generated method stub
}
protected String createDescription(Column column, List<CellAtRow> cellsAtRows) {
return "Create new column " + _headerLabel +
" based on column " + column.getHeaderLabel() +
" by filling " + cellsAtRows.size() +
" rows with " + _expression;
}
protected RowVisitor createRowVisitor(Project project, List<CellAtRow> cellsAtRows) throws Exception {
Evaluable eval = new Parser(_expression).getExpression();
Properties bindings = ExpressionUtils.createBindings(project);
return new RowVisitor() {
int cellIndex;
Properties bindings;
List<CellAtRow> cellsAtRows;
Evaluable eval;
public RowVisitor init(int cellIndex, Properties bindings, List<CellAtRow> cellsAtRows, Evaluable eval) {
this.cellIndex = cellIndex;
this.bindings = bindings;
this.cellsAtRows = cellsAtRows;
this.eval = eval;
return this;
}
@Override
public boolean visit(Project project, int rowIndex, Row row) {
if (cellIndex < row.cells.size()) {
Cell cell = row.cells.get(cellIndex);
if (cell.value != null) {
ExpressionUtils.bind(bindings, row, cell);
Cell newCell = new Cell(eval.evaluate(bindings), null);
cellsAtRows.add(new CellAtRow(rowIndex, newCell));
}
}
return false;
}
}.init(_baseCellIndex, bindings, cellsAtRows, eval);
}
}

View File

@ -0,0 +1,52 @@
package com.metaweb.gridworks.model.operations;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONWriter;
import com.metaweb.gridworks.history.Change;
import com.metaweb.gridworks.history.HistoryEntry;
import com.metaweb.gridworks.model.AbstractOperation;
import com.metaweb.gridworks.model.Column;
import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.changes.ColumnRemovalChange;
import com.metaweb.gridworks.process.Process;
import com.metaweb.gridworks.process.QuickHistoryEntryProcess;
public class ColumnRemovalOperation implements AbstractOperation {
private static final long serialVersionUID = 8422079695048733734L;
final protected int _columnRemovalIndex;
public ColumnRemovalOperation(
int columnRemoveIndex
) {
_columnRemovalIndex = columnRemoveIndex;
}
@Override
public Process createProcess(Project project, Properties options)
throws Exception {
Column column = project.columnModel.columns.get(_columnRemovalIndex);
if (column == null) {
throw new Exception("No column at index " + _columnRemovalIndex);
}
String description = "Remove column " + column.getHeaderLabel();
Change change = new ColumnRemovalChange(_columnRemovalIndex);
HistoryEntry historyEntry = new HistoryEntry(
project, description, this, change);
return new QuickHistoryEntryProcess(project, historyEntry);
}
@Override
public void write(JSONWriter writer, Properties options)
throws JSONException {
// TODO Auto-generated method stub
}
}

View File

@ -8,22 +8,7 @@ function onLoad() {
id: parseInt(params.project) id: parseInt(params.project)
}; };
Ajax.chainGetJSON( reinitializeProjectData(initializeUI);
"/command/get-project-metadata?" + $.param({ project: theProject.id }), null,
function(data) {
theProject.metadata = data;
},
"/command/get-column-model?" + $.param({ project: theProject.id }), null,
function(data) {
theProject.columnModel = data;
for (var i = 0; i < theProject.columnModel.columns.length; i++) {
theProject.columnModel.columns[i].collapsed = false;
}
},
function() {
initializeUI();
}
);
} }
} }
$(onLoad); $(onLoad);
@ -55,3 +40,20 @@ function initializeUI() {
ui.historyWidget = new HistoryWidget(ui.historyPanel); ui.historyWidget = new HistoryWidget(ui.historyPanel);
ui.dataTableView = new DataTableView(ui.viewPanel); ui.dataTableView = new DataTableView(ui.viewPanel);
} }
function reinitializeProjectData(f) {
Ajax.chainGetJSON(
"/command/get-project-metadata?" + $.param({ project: theProject.id }), null,
function(data) {
theProject.metadata = data;
},
"/command/get-column-model?" + $.param({ project: theProject.id }), null,
function(data) {
theProject.columnModel = data;
for (var i = 0; i < theProject.columnModel.columns.length; i++) {
theProject.columnModel.columns[i].collapsed = false;
}
},
f
);
}

View File

@ -6,7 +6,14 @@ function DataTableView(div) {
} }
DataTableView.prototype.update = function(reset) { DataTableView.prototype.update = function(reset) {
this._showRows(reset ? 0 : theProject.rowModel.start); if (reset) {
var self = this;
reinitializeProjectData(function() {
self._showRows(0);
});
} else {
this._showRows(theProject.rowModel.start);
}
}; };
DataTableView.prototype.render = function() { DataTableView.prototype.render = function() {
@ -117,7 +124,7 @@ DataTableView.prototype.render = function() {
} }
var renderCell = function(cell, td) { var renderCell = function(cell, td) {
if (cell.v == null) { if (cell == null || cell.v == null) {
return; return;
} }
@ -466,6 +473,15 @@ DataTableView.prototype._createMenuForColumnHeader = function(column, index, elm
] ]
}, },
{}, {},
{
label: "Add Column Based on This Column",
click: function() { self._doAddColumn(column, index, "value"); }
},
{
label: "Remove This Column",
click: function() { self._doRemoveColumn(column, index); }
},
{},
{ {
label: "Text Transform", label: "Text Transform",
submenu: [ submenu: [
@ -545,24 +561,34 @@ DataTableView.prototype._doFilterByExpressionPrompt = function(column, expressio
); );
}; };
DataTableView.prototype._createUpdateFunction = function() { DataTableView.prototype._createUpdateFunction = function(onBefore) {
var self = this; var self = this;
return function(data) { return function(data) {
if (data.code == "ok") { if (data.code == "ok") {
var onDone = function() {
self.update(); self.update();
ui.historyWidget.update(); ui.historyWidget.update();
};
} else { } else {
var onDone = function() {
ui.processWidget.update(); ui.processWidget.update();
} }
}
if (onBefore) {
onBefore(onDone);
} else {
onDone();
}
}; };
}; };
DataTableView.prototype._doPostThenUpdate = function(command, params) { DataTableView.prototype._doPostThenUpdate = function(command, params, updateColumnModel) {
params.project = theProject.id; params.project = theProject.id;
$.post( $.post(
"/command/" + command + "?" + $.param(params), "/command/" + command + "?" + $.param(params),
{ engine: JSON.stringify(ui.browsingEngine.getJSON()) }, { engine: JSON.stringify(ui.browsingEngine.getJSON()) },
this._createUpdateFunction(), this._createUpdateFunction(updateColumnModel ? reinitializeProjectData : undefined),
"json" "json"
); );
}; };
@ -637,3 +663,36 @@ DataTableView.prototype._doApproveNewTopics = function(column) {
{ cell: column.cellIndex } { cell: column.cellIndex }
); );
}; };
DataTableView.prototype._doAddColumn = function(column, index, initialExpression) {
var self = this;
DataTableView.promptExpressionOnVisibleRows(
column,
"Add Column Based on Column " + column.headerLabel,
initialExpression,
function(expression) {
var headerLabel = window.prompt("Enter header label for new column:");
if (headerLabel != null) {
self._doPostThenUpdate(
"add-column",
{
baseCellIndex: column.cellIndex,
expression: expression,
headerLabel: headerLabel,
columnInsertIndex: index + 1
},
true
);
}
}
);
};
DataTableView.prototype._doRemoveColumn = function(column, index) {
this._doPostThenUpdate(
"remove-column",
{ columnRemovalIndex: index },
true
);
};

View File

@ -72,7 +72,7 @@ HistoryWidget.prototype._onClickHistoryEntry = function(evt, entry, lastDoneID)
function(data) { function(data) {
if (data.code == "ok") { if (data.code == "ok") {
self.update(); self.update();
ui.dataTableView.update(); ui.dataTableView.update(true);
} else { } else {
// update process UI // update process UI
} }

View File

@ -81,6 +81,9 @@ MenuSystem.createAndShowStandardMenu = function(items, elmt, options) {
if ("tooltip" in item) { if ("tooltip" in item) {
menuItem.attr("title", item.tooltip); menuItem.attr("title", item.tooltip);
} }
menuItem.mouseover(function() {
MenuSystem.dismissUntil(level);
});
} }
} else if ("heading" in item) { } else if ("heading" in item) {
$('<div></div>').addClass("menu-section").text(item.heading).appendTo(menu); $('<div></div>').addClass("menu-section").text(item.heading).appendTo(menu);