From b3ac945c33834e65425f3b02f7c84c8abef367aa Mon Sep 17 00:00:00 2001 From: David Huynh Date: Fri, 5 Mar 2010 08:11:48 +0000 Subject: [PATCH] Implemented single-cell editing. git-svn-id: http://google-refine.googlecode.com/svn/trunk@210 7d457c2a-affb-35e4-300a-418c747d4874 --- .../metaweb/gridworks/GridworksServlet.java | 2 + .../commands/edit/EditOneCellCommand.java | 115 +++++++++++++++++ .../com/metaweb/gridworks/model/Cell.java | 13 +- src/main/webapp/project.html | 2 +- src/main/webapp/scripts/util/menu.js | 39 ++++-- .../scripts/views/data-table-cell-ui.js | 118 ++++++++++++++++-- src/main/webapp/styles/data-table-view.css | 34 +++++ 7 files changed, 302 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/metaweb/gridworks/commands/edit/EditOneCellCommand.java diff --git a/src/main/java/com/metaweb/gridworks/GridworksServlet.java b/src/main/java/com/metaweb/gridworks/GridworksServlet.java index f5d92322c..b1d0de670 100644 --- a/src/main/java/com/metaweb/gridworks/GridworksServlet.java +++ b/src/main/java/com/metaweb/gridworks/GridworksServlet.java @@ -16,6 +16,7 @@ import com.metaweb.gridworks.commands.edit.AnnotateRowsCommand; import com.metaweb.gridworks.commands.edit.ApplyOperationsCommand; import com.metaweb.gridworks.commands.edit.CreateProjectCommand; import com.metaweb.gridworks.commands.edit.DoTextTransformCommand; +import com.metaweb.gridworks.commands.edit.EditOneCellCommand; import com.metaweb.gridworks.commands.edit.FacetBasedEditCommand; import com.metaweb.gridworks.commands.edit.JoinMultiValueCellsCommand; import com.metaweb.gridworks.commands.edit.RemoveColumnCommand; @@ -75,6 +76,7 @@ public class GridworksServlet extends HttpServlet { _commands.put("compute-facets", new ComputeFacetsCommand()); _commands.put("do-text-transform", new DoTextTransformCommand()); _commands.put("facet-based-edit", new FacetBasedEditCommand()); + _commands.put("edit-one-cell", new EditOneCellCommand()); _commands.put("add-column", new AddColumnCommand()); _commands.put("remove-column", new RemoveColumnCommand()); diff --git a/src/main/java/com/metaweb/gridworks/commands/edit/EditOneCellCommand.java b/src/main/java/com/metaweb/gridworks/commands/edit/EditOneCellCommand.java new file mode 100644 index 000000000..b206e0c63 --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/commands/edit/EditOneCellCommand.java @@ -0,0 +1,115 @@ +package com.metaweb.gridworks.commands.edit; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Properties; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONWriter; + +import com.metaweb.gridworks.commands.Command; +import com.metaweb.gridworks.expr.ExpressionUtils; +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.Recon; +import com.metaweb.gridworks.model.changes.CellChange; +import com.metaweb.gridworks.process.QuickHistoryEntryProcess; +import com.metaweb.gridworks.util.ParsingUtilities; + +public class EditOneCellCommand extends Command { + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + try { + Project project = getProject(request); + + int rowIndex = Integer.parseInt(request.getParameter("row")); + int cellIndex = Integer.parseInt(request.getParameter("cell")); + + String type = request.getParameter("type"); + String valueString = request.getParameter("value"); + Serializable value = null; + + if ("number".equals(type)) { + value = Double.parseDouble(valueString); + } else if ("boolean".equals(type)) { + value = "true".equalsIgnoreCase(valueString); + } else if ("date".equals(type)) { + value = ParsingUtilities.stringToDate(valueString); + } else { + value = valueString; + } + + EditOneCellProcess process = new EditOneCellProcess( + project, + "Edit single cell", + rowIndex, + cellIndex, + value + ); + + boolean done = project.processManager.queueProcess(process); + if (done) { + JSONWriter writer = new JSONWriter(response.getWriter()); + writer.object(); + writer.key("code"); writer.value("ok"); + writer.key("cell"); process.newCell.write(writer, new Properties()); + writer.endObject(); + } else { + respond(response, "{ \"code\" : \"pending\" }"); + } + } catch (Exception e) { + respondException(response, e); + } + } + + protected class EditOneCellProcess extends QuickHistoryEntryProcess { + final int rowIndex; + final int cellIndex; + final Serializable value; + Cell newCell; + + EditOneCellProcess( + Project project, + String briefDescription, + int rowIndex, + int cellIndex, + Serializable value + ) { + super(project, briefDescription); + + this.rowIndex = rowIndex; + this.cellIndex = cellIndex; + this.value = value; + } + + protected HistoryEntry createHistoryEntry() throws Exception { + Cell cell = _project.rows.get(rowIndex).getCell(cellIndex); + Column column = _project.columnModel.getColumnByCellIndex(cellIndex); + if (column == null) { + throw new Exception("No such column"); + } + + newCell = new Cell( + value, + cell != null ? cell.recon : null + ); + + String description = + "Edit single cell on row " + (rowIndex + 1) + + ", column " + column.getHeaderLabel(); + + Change change = new CellChange(rowIndex, cellIndex, cell, newCell); + + return new HistoryEntry( + _project, description, null, change); + } + } +} diff --git a/src/main/java/com/metaweb/gridworks/model/Cell.java b/src/main/java/com/metaweb/gridworks/model/Cell.java index abe6d0799..940bf8986 100644 --- a/src/main/java/com/metaweb/gridworks/model/Cell.java +++ b/src/main/java/com/metaweb/gridworks/model/Cell.java @@ -2,6 +2,7 @@ package com.metaweb.gridworks.model; import java.io.Serializable; import java.util.Calendar; +import java.util.Date; import java.util.Properties; import org.json.JSONException; @@ -40,10 +41,16 @@ public class Cell implements Serializable, HasFields, Jsonizable { writer.value(((EvalError) value).message); } else { writer.key("v"); - if (value != null && value instanceof Calendar) { - writer.value(ParsingUtilities.dateToString(((Calendar) value).getTime())); + if (value != null) { + if (value instanceof Calendar) { + writer.value(ParsingUtilities.dateToString(((Calendar) value).getTime())); + } else if (value instanceof Date) { + writer.value(ParsingUtilities.dateToString((Date) value)); + } else { + writer.value(value); + } } else { - writer.value(value); + writer.value(null); } } diff --git a/src/main/webapp/project.html b/src/main/webapp/project.html index 49b5071a1..8f936c916 100644 --- a/src/main/webapp/project.html +++ b/src/main/webapp/project.html @@ -1 +1 @@ - Gridworks
starting up ...
\ No newline at end of file + Gridworks
starting up ...
\ No newline at end of file diff --git a/src/main/webapp/scripts/util/menu.js b/src/main/webapp/scripts/util/menu.js index f485615f7..b8ecee94a 100644 --- a/src/main/webapp/scripts/util/menu.js +++ b/src/main/webapp/scripts/util/menu.js @@ -21,8 +21,6 @@ MenuSystem.showMenu = function(elmt, onDismiss) { var level = MenuSystem._layers.length; - elmt.click(MenuSystem.dismissAll); - return level; }; @@ -53,14 +51,38 @@ MenuSystem.createMenuItem = function() { MenuSystem.positionMenuAboveBelow = function(menu, elmt) { var offset = elmt.offset(); - menu.css("left", offset.left + "px") - .css("top", (offset.top + elmt.outerHeight()) + "px"); + var windowWidth = $(document.body).innerWidth(); + var windowHeight = $(document.body).innerHeight(); + + if (offset.top + elmt.outerHeight() - document.body.scrollTop + menu.outerHeight() > windowHeight - 10) { + menu.css("top", (offset.top - menu.outerHeight()) + "px"); + } else { + menu.css("top", (offset.top + elmt.outerHeight()) + "px"); + } + + if (offset.left - document.body.scrollLeft + menu.outerWidth() > windowWidth - 10) { + menu.css("left", (offset.left + elmt.outerWidth() - menu.outerWidth()) + "px"); + } else { + menu.css("left", offset.left + "px"); + } }; MenuSystem.positionMenuLeftRight = function(menu, elmt) { var offset = elmt.offset(); - menu.css("top", offset.top + "px") - .css("left", (offset.left + elmt.outerWidth()) + "px"); + var windowWidth = $(document.body).innerWidth(); + var windowHeight = $(document.body).innerHeight(); + + if (offset.top - document.body.scrollTop + menu.outerHeight() > windowHeight - 10) { + menu.css("top", (offset.top + elmt.outerHeight() - menu.outerHeight()) + "px"); + } else { + menu.css("top", offset.top + "px"); + } + + if (offset.left + elmt.outerWidth() - document.body.scrollLeft + menu.outerWidth() > windowWidth - 10) { + menu.css("left", (offset.left - menu.outerWidth()) + "px"); + } else { + menu.css("left", (offset.left + elmt.outerWidth()) + "px"); + } }; MenuSystem.createAndShowStandardMenu = function(items, elmt, options) { @@ -100,7 +122,10 @@ MenuSystem.createAndShowStandardMenu = function(items, elmt, options) { ); }); } else { - menuItem.html(item.label).click(item.click); + menuItem.html(item.label).click(function(evt) { + item.click.call(this, evt); + MenuSystem.dismissAll(); + }); if ("tooltip" in item) { menuItem.attr("title", item.tooltip); } diff --git a/src/main/webapp/scripts/views/data-table-cell-ui.js b/src/main/webapp/scripts/views/data-table-cell-ui.js index 4b07f38a0..9e7ff9095 100644 --- a/src/main/webapp/scripts/views/data-table-cell-ui.js +++ b/src/main/webapp/scripts/views/data-table-cell-ui.js @@ -12,21 +12,31 @@ DataTableCellUI.prototype._render = function() { var self = this; var cell = this._cell; - $(this._td).empty(); - var divContent = $('
').appendTo(this._td); + var divContent = $('
') + .addClass("data-table-cell-content"); + + var editLink = $('') + .addClass("data-table-cell-edit") + .text("edit") + .appendTo(divContent) + .click(function() { self._startEdit(this); }); + + $(this._td).empty() + .unbind() + .mouseenter(function() { editLink.css("visibility", "visible"); }) + .mouseleave(function() { editLink.css("visibility", "hidden"); }); if (cell == null || ("v" in cell && cell.v == null)) { - $(divContent).html(" "); + $('').html(" ").appendTo(divContent); } else if ("e" in cell) { $('').addClass("data-table-error").text(cell.e).appendTo(divContent); } else if (!("r" in cell) || cell.r == null) { - $(divContent).text(cell.v); + $('').text(cell.v).appendTo(divContent); } else { var r = cell.r; if (r.j == "new") { - $(divContent).text(cell.v + " (new topic)"); + $('').text(cell.v + " (new topic) ").appendTo(divContent); - $(' ').appendTo(divContent); $('re-match') .addClass("data-table-recon-action") .appendTo(divContent).click(function(evt) { @@ -35,19 +45,21 @@ DataTableCellUI.prototype._render = function() { } else if (r.j == "matched" && "m" in r && r.m != null) { var match = cell.r.m; $('') + .text(match.name) .attr("href", "http://www.freebase.com/view" + match.id) .attr("target", "_blank") - .text(match.name) .appendTo(divContent); $(' ').appendTo(divContent); - $('re-match') + $('') + .text("re-match") .addClass("data-table-recon-action") - .appendTo(divContent).click(function(evt) { + .appendTo(divContent) + .click(function(evt) { self._doRematch(); }); } else { - $(divContent).text(cell.v); + $('').text(cell.v).appendTo(divContent); if (this._dataTableView._showRecon) { var ul = $('
').addClass("data-table-recon-candidates").appendTo(divContent); @@ -118,6 +130,8 @@ DataTableCellUI.prototype._render = function() { } } } + + divContent.appendTo(this._td) }; DataTableCellUI.prototype._doRematch = function() { @@ -270,3 +284,87 @@ DataTableCellUI.prototype._previewCandidateTopic = function(id, elmt) { MenuSystem.showMenu(fakeMenu, function(){}); MenuSystem.positionMenuLeftRight(fakeMenu, $(elmt)); }; + +DataTableCellUI.prototype._startEdit = function(elmt) { + self = this; + + var menu = MenuSystem.createMenu().width("300px"); + menu.html( + '