From e24d40c3da38507455908a8200e243f3a69a24fc Mon Sep 17 00:00:00 2001 From: David Huynh Date: Sat, 30 Jan 2010 01:05:30 +0000 Subject: [PATCH] Faceted browsing is starting to work. git-svn-id: http://google-refine.googlecode.com/svn/trunk@13 7d457c2a-affb-35e4-300a-418c747d4874 --- .../com/metaweb/gridlock/GridlockServlet.java | 2 + .../com/metaweb/gridlock/browsing/Engine.java | 8 +- .../metaweb/gridlock/browsing/RowVisitor.java | 2 +- .../facets/CellAccessorNominalRowGrouper.java | 3 +- .../facets/ExpressionNominalRowGrouper.java | 30 ++-- .../gridlock/browsing/facets/ListFacet.java | 16 +- .../metaweb/gridlock/commands/Command.java | 8 +- .../commands/ComputeFacetsCommand.java | 30 ++++ .../gridlock/commands/GetRowsCommand.java | 13 +- .../com/metaweb/gridlock/expr/Parser.java | 6 +- .../metaweb/gridlock/expr/functions/Get.java | 67 +++++++++ .../gridlock/expr/functions/Slice.java | 7 +- .../gridlock/expr/functions/Split.java | 21 +++ src/main/webapp/project.html | 2 +- .../webapp/scripts/project/browsing-engine.js | 37 ++++- .../webapp/scripts/project/data-table-view.js | 42 +++++- .../webapp/scripts/project/history-widget.js | 6 + src/main/webapp/scripts/project/list-facet.js | 142 ++++++++++++++++++ src/main/webapp/scripts/util/misc.js | 23 +++ src/main/webapp/styles/browsing.css | 59 ++++++++ src/main/webapp/styles/history.css | 35 +++++ src/main/webapp/styles/project.css | 35 ----- 22 files changed, 515 insertions(+), 79 deletions(-) create mode 100644 src/main/java/com/metaweb/gridlock/commands/ComputeFacetsCommand.java create mode 100644 src/main/java/com/metaweb/gridlock/expr/functions/Get.java create mode 100644 src/main/java/com/metaweb/gridlock/expr/functions/Split.java create mode 100644 src/main/webapp/scripts/project/list-facet.js create mode 100644 src/main/webapp/scripts/util/misc.js create mode 100644 src/main/webapp/styles/browsing.css create mode 100644 src/main/webapp/styles/history.css diff --git a/src/main/java/com/metaweb/gridlock/GridlockServlet.java b/src/main/java/com/metaweb/gridlock/GridlockServlet.java index 2210eda11..7071a65c4 100644 --- a/src/main/java/com/metaweb/gridlock/GridlockServlet.java +++ b/src/main/java/com/metaweb/gridlock/GridlockServlet.java @@ -15,6 +15,7 @@ import org.json.JSONObject; import org.json.JSONTokener; import com.metaweb.gridlock.commands.Command; +import com.metaweb.gridlock.commands.ComputeFacetsCommand; import com.metaweb.gridlock.commands.CreateProjectFromUploadCommand; import com.metaweb.gridlock.commands.DoTextTransformCommand; import com.metaweb.gridlock.commands.GetColumnModelCommand; @@ -34,6 +35,7 @@ public class GridlockServlet extends HttpServlet { _commands.put("get-column-model", new GetColumnModelCommand()); _commands.put("get-rows", new GetRowsCommand()); _commands.put("get-history", new GetHistoryCommand()); + _commands.put("compute-facets", new ComputeFacetsCommand()); _commands.put("undo-redo", new UndoRedoCommand()); _commands.put("do-text-transform", new DoTextTransformCommand()); } diff --git a/src/main/java/com/metaweb/gridlock/browsing/Engine.java b/src/main/java/com/metaweb/gridlock/browsing/Engine.java index 4652cf2e3..e9a8b4b6e 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/Engine.java +++ b/src/main/java/com/metaweb/gridlock/browsing/Engine.java @@ -11,6 +11,7 @@ import org.json.JSONObject; import com.metaweb.gridlock.browsing.facets.Facet; import com.metaweb.gridlock.browsing.facets.ListFacet; +import com.metaweb.gridlock.browsing.filters.RowFilter; import com.metaweb.gridlock.model.Project; public class Engine { @@ -29,7 +30,10 @@ public class Engine { ConjunctiveFilteredRows cfr = new ConjunctiveFilteredRows(); for (Facet facet : _facets) { if (facet != except) { - cfr.add(facet.getRowFilter()); + RowFilter rowFilter = facet.getRowFilter(); + if (rowFilter != null) { + cfr.add(rowFilter); + } } } return cfr; @@ -53,7 +57,7 @@ public class Engine { for (int i = 0; i < length; i++) { JSONObject fo = a.getJSONObject(i); - String type = fo.getString("type"); + String type = fo.has("type") ? fo.getString("type") : "list"; Facet facet = null; if ("list".equals(type)) { diff --git a/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java b/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java index d7c2bb1b5..3e2d1a491 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java +++ b/src/main/java/com/metaweb/gridlock/browsing/RowVisitor.java @@ -3,6 +3,6 @@ package com.metaweb.gridlock.browsing; import com.metaweb.gridlock.model.Row; public interface RowVisitor { - public void visit(int rowIndex, Row row); + public boolean visit(int rowIndex, Row row); } diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java b/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java index bf7c68294..bd86cc5b5 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/CellAccessorNominalRowGrouper.java @@ -21,7 +21,7 @@ public class CellAccessorNominalRowGrouper implements RowVisitor { } @Override - public void visit(int rowIndex, Row row) { + public boolean visit(int rowIndex, Row row) { if (_cellIndex < row.cells.size()) { Cell cell = row.cells.get(_cellIndex); if (cell != null) { @@ -48,5 +48,6 @@ public class CellAccessorNominalRowGrouper implements RowVisitor { } } } + return false; } } diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java b/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java index 7b26a2756..233aa851f 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/ExpressionNominalRowGrouper.java @@ -22,7 +22,7 @@ public class ExpressionNominalRowGrouper implements RowVisitor { } @Override - public void visit(int rowIndex, Row row) { + public boolean visit(int rowIndex, Row row) { if (_cellIndex < row.cells.size()) { Cell cell = row.cells.get(_cellIndex); if (cell != null) { @@ -33,18 +33,30 @@ public class ExpressionNominalRowGrouper implements RowVisitor { Object value = _evaluable.evaluate(bindings); if (value != null) { - DecoratedValue dValue = new DecoratedValue(value, value.toString()); - - if (choices.containsKey(value)) { - choices.get(value).count++; + if (value.getClass().isArray()) { + Object[] a = (Object[]) value; + for (Object v : a) { + processValue(v); + } } else { - NominalFacetChoice choice = new NominalFacetChoice(dValue); - choice.count = 1; - - choices.put(value, choice); + processValue(value); } } } } + return false; + } + + protected void processValue(Object value) { + DecoratedValue dValue = new DecoratedValue(value, value.toString()); + + if (choices.containsKey(value)) { + choices.get(value).count++; + } else { + NominalFacetChoice choice = new NominalFacetChoice(dValue); + choice.count = 1; + + choices.put(value, choice); + } } } diff --git a/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java b/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java index a31ea6b42..a9f4cf05d 100644 --- a/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java +++ b/src/main/java/com/metaweb/gridlock/browsing/facets/ListFacet.java @@ -76,7 +76,8 @@ public class ListFacet implements Facet { @Override public RowFilter getRowFilter() { - return new ExpressionEqualRowFilter(_eval, _cellIndex, createMatches()); + return _selection.size() == 0 ? null : + new ExpressionEqualRowFilter(_eval, _cellIndex, createMatches()); } @Override @@ -88,12 +89,21 @@ public class ListFacet implements Facet { _choices.clear(); _choices.addAll(grouper.choices.values()); + + for (NominalFacetChoice choice : _selection) { + if (grouper.choices.containsKey(choice.decoratedValue.value)) { + grouper.choices.get(choice.decoratedValue.value).selected = true; + } else { + choice.count = 0; + _choices.add(choice); + } + } } protected Object[] createMatches() { - Object[] a = new Object[_choices.size()]; + Object[] a = new Object[_selection.size()]; for (int i = 0; i < a.length; i++) { - a[i] = _choices.get(i).decoratedValue.value; + a[i] = _selection.get(i).decoratedValue.value; } return a; } diff --git a/src/main/java/com/metaweb/gridlock/commands/Command.java b/src/main/java/com/metaweb/gridlock/commands/Command.java index 50223246b..2d340537e 100644 --- a/src/main/java/com/metaweb/gridlock/commands/Command.java +++ b/src/main/java/com/metaweb/gridlock/commands/Command.java @@ -153,14 +153,10 @@ public abstract class Command { } protected Engine getEngine(HttpServletRequest request, Project project) throws Exception { - Properties properties = new Properties(); - readFileUpload(request, properties); - Engine engine = new Engine(project); - if (properties.containsKey("engine")) { - String json = properties.getProperty("engine"); + String json = request.getParameter("engine"); + if (json != null) { JSONObject o = jsonStringToObject(json); - engine.initializeFromJSON(o); } return engine; diff --git a/src/main/java/com/metaweb/gridlock/commands/ComputeFacetsCommand.java b/src/main/java/com/metaweb/gridlock/commands/ComputeFacetsCommand.java new file mode 100644 index 000000000..1b7da0382 --- /dev/null +++ b/src/main/java/com/metaweb/gridlock/commands/ComputeFacetsCommand.java @@ -0,0 +1,30 @@ +package com.metaweb.gridlock.commands; + +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.gridlock.browsing.Engine; +import com.metaweb.gridlock.model.Project; + +public class ComputeFacetsCommand extends Command { + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + try { + Project project = getProject(request); + Engine engine = getEngine(request, project); + + engine.computeFacets(); + + Properties options = new Properties(); + respondJSON(response, engine.getJSON(options)); + } catch (Exception e) { + respondException(response, e); + } + } +} diff --git a/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java b/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java index 32d13a587..20966447c 100644 --- a/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java +++ b/src/main/java/com/metaweb/gridlock/commands/GetRowsCommand.java @@ -47,13 +47,14 @@ public class GetRowsCommand extends Command { } @Override - public void internalVisit(int rowIndex, Row row) { + public boolean internalVisit(int rowIndex, Row row) { try { JSONObject ro = row.getJSON(options); ro.put("i", rowIndex); list.add(ro); } catch (JSONException e) { } + return false; } }.init(a, options); @@ -86,14 +87,18 @@ public class GetRowsCommand extends Command { } @Override - public void visit(int rowIndex, Row row) { + public boolean visit(int rowIndex, Row row) { + boolean r = false; + if (total >= start && total < start + limit) { - internalVisit(rowIndex, row); + r = internalVisit(rowIndex, row); } total++; + return r; } - protected void internalVisit(int rowIndex, Row row) { + protected boolean internalVisit(int rowIndex, Row row) { + return false; } } } diff --git a/src/main/java/com/metaweb/gridlock/expr/Parser.java b/src/main/java/com/metaweb/gridlock/expr/Parser.java index a6e1abedf..375e90feb 100644 --- a/src/main/java/com/metaweb/gridlock/expr/Parser.java +++ b/src/main/java/com/metaweb/gridlock/expr/Parser.java @@ -8,8 +8,10 @@ import java.util.Map; import com.metaweb.gridlock.expr.Scanner.NumberToken; import com.metaweb.gridlock.expr.Scanner.Token; import com.metaweb.gridlock.expr.Scanner.TokenType; +import com.metaweb.gridlock.expr.functions.Get; import com.metaweb.gridlock.expr.functions.Replace; import com.metaweb.gridlock.expr.functions.Slice; +import com.metaweb.gridlock.expr.functions.Split; import com.metaweb.gridlock.expr.functions.ToLowercase; import com.metaweb.gridlock.expr.functions.ToTitlecase; import com.metaweb.gridlock.expr.functions.ToUppercase; @@ -26,7 +28,9 @@ public class Parser { functionTable.put("toTitlecase", new ToTitlecase()); functionTable.put("slice", new Slice()); functionTable.put("substring", new Slice()); + functionTable.put("get", new Get()); functionTable.put("replace", new Replace()); + functionTable.put("split", new Split()); } public Parser(String s) throws Exception { @@ -196,7 +200,7 @@ public class Parser { List args = parseExpressionList("]"); args.add(0, eval); - eval = new FunctionCallExpr(makeArray(args), functionTable.get("slice")); + eval = new FunctionCallExpr(makeArray(args), functionTable.get("get")); } else { break; } diff --git a/src/main/java/com/metaweb/gridlock/expr/functions/Get.java b/src/main/java/com/metaweb/gridlock/expr/functions/Get.java new file mode 100644 index 000000000..2b42c586a --- /dev/null +++ b/src/main/java/com/metaweb/gridlock/expr/functions/Get.java @@ -0,0 +1,67 @@ +package com.metaweb.gridlock.expr.functions; + +import java.util.Properties; + +import com.metaweb.gridlock.expr.Function; + +public class Get implements Function { + + @Override + public Object call(Properties bindings, Object[] args) { + if (args.length > 1 && args.length <= 3) { + Object v = args[0]; + Object from = args[1]; + Object to = args.length == 3 ? args[2] : null; + + if (v != null && from != null && from instanceof Number && (to == null || to instanceof Number)) { + if (v.getClass().isArray()) { + Object[] a = (Object[]) v; + int start = ((Number) from).intValue(); + if (start < 0) { + start = a.length + start; + } + start = Math.min(a.length, Math.max(0, start)); + + if (to == null) { + return a[start]; + } else { + int end = to != null && to instanceof Number ? + ((Number) to).intValue() : a.length; + + if (end < 0) { + end = a.length - end; + } + end = Math.min(a.length, Math.max(start, end)); + + Object[] a2 = new Object[end - start]; + System.arraycopy(a, start, a2, 0, end - start); + + return a2; + } + } else { + String s = (v instanceof String ? (String) v : v.toString()); + + int start = ((Number) from).intValue(); + if (start < 0) { + start = s.length() + start; + } + start = Math.min(s.length(), Math.max(0, start)); + + if (to != null && to instanceof Number) { + int end = ((Number) to).intValue(); + if (end < 0) { + end = s.length() - end; + } + end = Math.min(s.length(), Math.max(start, end)); + + return s.substring(start, end); + } else { + return s.substring(start, start + 1); + } + } + } + } + return null; + } + +} diff --git a/src/main/java/com/metaweb/gridlock/expr/functions/Slice.java b/src/main/java/com/metaweb/gridlock/expr/functions/Slice.java index 332e313e1..1ef8464c7 100644 --- a/src/main/java/com/metaweb/gridlock/expr/functions/Slice.java +++ b/src/main/java/com/metaweb/gridlock/expr/functions/Slice.java @@ -1,6 +1,5 @@ package com.metaweb.gridlock.expr.functions; -import java.lang.reflect.Array; import java.util.Properties; import com.metaweb.gridlock.expr.Function; @@ -15,14 +14,14 @@ public class Slice implements Function { Object to = args.length == 3 ? args[2] : null; if (v != null && from != null && from instanceof Number && (to == null || to instanceof Number)) { - if (v instanceof Array) { + if (v.getClass().isArray()) { Object[] a = (Object[]) v; int start = ((Number) from).intValue(); int end = to != null && to instanceof Number ? ((Number) to).intValue() : a.length; if (start < 0) { - start = a.length - start; + start = a.length + start; } start = Math.min(a.length, Math.max(0, start)); @@ -40,7 +39,7 @@ public class Slice implements Function { int start = ((Number) from).intValue(); if (start < 0) { - start = s.length() - start; + start = s.length() + start; } start = Math.min(s.length(), Math.max(0, start)); diff --git a/src/main/java/com/metaweb/gridlock/expr/functions/Split.java b/src/main/java/com/metaweb/gridlock/expr/functions/Split.java new file mode 100644 index 000000000..615370856 --- /dev/null +++ b/src/main/java/com/metaweb/gridlock/expr/functions/Split.java @@ -0,0 +1,21 @@ +package com.metaweb.gridlock.expr.functions; + +import java.util.Properties; + +import com.metaweb.gridlock.expr.Function; + +public class Split implements Function { + + @Override + public Object call(Properties bindings, Object[] args) { + if (args.length == 2) { + Object v = args[0]; + Object split = args[1]; + if (v != null && split != null && split instanceof String) { + return (v instanceof String ? (String) v : v.toString()).split((String) split); + } + } + return null; + } + +} diff --git a/src/main/webapp/project.html b/src/main/webapp/project.html index 2bfb12442..67fbdef4e 100644 --- a/src/main/webapp/project.html +++ b/src/main/webapp/project.html @@ -1 +1 @@ - Gridlock
Loading ...
\ No newline at end of file + Gridlock
Loading ...
\ No newline at end of file diff --git a/src/main/webapp/scripts/project/browsing-engine.js b/src/main/webapp/scripts/project/browsing-engine.js index d771e9a82..105c9e99d 100644 --- a/src/main/webapp/scripts/project/browsing-engine.js +++ b/src/main/webapp/scripts/project/browsing-engine.js @@ -1,11 +1,11 @@ function BrowsingEngine(div) { this._div = div; - this.render(); - this._facets = []; + + this._initializeUI(); } -BrowsingEngine.prototype.render = function() { +BrowsingEngine.prototype._initializeUI = function() { var self = this; var container = this._div.empty(); }; @@ -13,7 +13,36 @@ BrowsingEngine.prototype.render = function() { BrowsingEngine.prototype.getJSON = function() { var a = { facets: [] }; for (var i = 0; i < this._facets.length; i++) { - a.facets.push(this._facets[i].getJSON()); + a.facets.push(this._facets[i].facet.getJSON()); } return a; }; + +BrowsingEngine.prototype.addFacet = function(type, config) { + var div = $('
').addClass("facet-container").appendTo(this._div); + var facet; + switch (type) { + default: + facet = new ListFacet(div, config, {}); + } + + this._facets.push({ elmt: div, facet: facet }); + this.update(); +}; + +BrowsingEngine.prototype.update = function() { + var self = this; + + $.post( + "/command/compute-facets?" + $.param({ project: theProject.id }), + { engine: JSON.stringify(ui.browsingEngine.getJSON()) }, + function(data) { + var facetData = data.facets; + + for (var i = 0; i < facetData.length; i++) { + self._facets[i].facet.updateState(facetData[i]); + } + }, + "json" + ); +}; diff --git a/src/main/webapp/scripts/project/data-table-view.js b/src/main/webapp/scripts/project/data-table-view.js index c3688cfec..3f4f587a9 100644 --- a/src/main/webapp/scripts/project/data-table-view.js +++ b/src/main/webapp/scripts/project/data-table-view.js @@ -11,7 +11,7 @@ DataTableView.prototype.render = function() { var divSummary = $('
').addClass("viewPanel-summary").appendTo(container); $('' + (theProject.rowModel.start + 1) + " to " + - (theProject.rowModel.start + theProject.rowModel.limit) + " of " + + Math.min(theProject.rowModel.filtered, theProject.rowModel.start + theProject.rowModel.limit) + " of " + (theProject.rowModel.filtered) + " filtered rows, " + (theProject.rowModel.total) + " total rows" + '' @@ -30,7 +30,7 @@ DataTableView.prototype.render = function() { $('').appendTo(pagingControls); var nextPage = $('next page »').appendTo(pagingControls); var lastPage = $('last »').appendTo(pagingControls); - if (theProject.rowModel.start + theProject.rowModel.limit < theProject.rowModel.total) { + if (theProject.rowModel.start + theProject.rowModel.limit < theProject.rowModel.filtered) { nextPage.addClass("action").click(function(evt) { self._onClickNextPage(this, evt); }); lastPage.addClass("action").click(function(evt) { self._onClickLastPage(this, evt); }); } else { @@ -137,7 +137,7 @@ DataTableView.prototype._onClickFirstPage = function(elmt, evt) { }; DataTableView.prototype._onClickLastPage = function(elmt, evt) { - this._showRows(Math.floor(theProject.rowModel.total / this._pageSize) * this._pageSize); + this._showRows(Math.floor(theProject.rowModel.filtered / this._pageSize) * this._pageSize); }; DataTableView.prototype._createMenuForAllColumns = function(elmt) { @@ -172,15 +172,29 @@ DataTableView.prototype._createMenuForColumnHeader = function(column, index, elm submenu: [ { label: "By Nominal Choices", - click: function() {} + click: function() { + ui.browsingEngine.addFacet( + "list", + { + "name" : column.headerLabel, + "cellIndex" : column.cellIndex, + "expression" : "value" + } + ); + } }, { label: "By Simple Text Search", click: function() {} }, { - label: "By Regular Expression", - click: function() {} + label: "By Custom Expression", + click: function() { + var expression = window.prompt("Enter expression", 'value'); + if (expression != null) { + self._doFilterByExpression(column, expression); + } + } }, { label: "By Reconciliation Features", @@ -282,6 +296,18 @@ DataTableView.prototype._doTextTransform = function(column, expression) { ); }; -DataTableView.prototype.update = function() { - this._showRows(theProject.rowModel.start); +DataTableView.prototype._doFilterByExpression = function(column, expression) { + ui.browsingEngine.addFacet( + "list", + { + "name" : column.headerLabel + ": " + expression, + "cellIndex" : column.cellIndex, + "expression" : expression + } + ); }; + +DataTableView.prototype.update = function(reset) { + this._showRows(reset ? 0 : theProject.rowModel.start); +}; + diff --git a/src/main/webapp/scripts/project/history-widget.js b/src/main/webapp/scripts/project/history-widget.js index 129222715..11075fd3c 100644 --- a/src/main/webapp/scripts/project/history-widget.js +++ b/src/main/webapp/scripts/project/history-widget.js @@ -1,5 +1,11 @@ function HistoryWidget(div) { this._div = div; + this._div.mouseover(function() { + this.style.height = "300px"; + }).mouseout(function() { + this.style.height = "100px"; + }); + this.update(); } diff --git a/src/main/webapp/scripts/project/list-facet.js b/src/main/webapp/scripts/project/list-facet.js new file mode 100644 index 000000000..c77e929a7 --- /dev/null +++ b/src/main/webapp/scripts/project/list-facet.js @@ -0,0 +1,142 @@ +function ListFacet(div, config, options) { + this._div = div; + this._config = config; + this._selection = []; + this._data = null; + + this.render(); +} + +ListFacet.prototype.getJSON = function() { + var o = cloneDeep(this._config); + o.type = "list"; + o.selection = []; + for (var i = 0; i < this._selection.length; i++) { + var choice = cloneDeep(this._selection[i]); + choice.s = true; + o.selection.push(choice); + } + return o; +}; + +ListFacet.prototype.updateState = function(data) { + this._data = data; + + var selection = []; + var choices = data.choices; + for (var i = 0; i < choices.length; i++) { + var choice = choices[i]; + if (choice.s) { + selection.push(choice); + } + } + this._selection = selection; + + this.render(); +}; + +ListFacet.prototype.render = function() { + var self = this; + + var scrollTop = 0; + try { + scrollTop = this._div[0].childNodes[1].scrollTop; + } catch (e) { + } + var container = this._div.empty(); + + var headerDiv = $('
').addClass("facet-title").appendTo(container); + $('').text(this._config.name).appendTo(headerDiv); + + var bodyDiv = $('
').addClass("facet-body").appendTo(container); + if (this._data == null) { + bodyDiv.html("Loading..."); + } else { + var selectionCount = this._selection.length; + if (selectionCount > 0) { + var reset = function() { + self._reset(); + }; + $('').addClass("facet-choice-link").text("reset").click(reset).prependTo(headerDiv); + } + + var renderChoice = function(choice) { + var label = choice.v.l; + var count = choice.c; + + var choiceDiv = $('
').addClass("facet-choice").appendTo(bodyDiv); + if (choice.s) { + choiceDiv.addClass("facet-choice-selected"); + } + + var a = $('').addClass("facet-choice-label").text(label).appendTo(choiceDiv); + $('').addClass("facet-choice-count").text(count).appendTo(choiceDiv); + + var select = function() { + self._select(choice, false); + }; + var selectOnly = function() { + self._select(choice, true); + }; + var deselect = function() { + self._deselect(choice); + }; + + if (choice.s) { // selected + if (selectionCount > 1) { + // select only + a.click(selectOnly); + } else { + // deselect + a.click(deselect); + } + + // remove link + $('').addClass("facet-choice-link").text("remove").click(deselect).prependTo(choiceDiv); + } else if (selectionCount > 0) { + a.click(selectOnly); + + // include link + $('').addClass("facet-choice-link").text("include").click(select).appendTo(choiceDiv); + } else { + a.click(select); + } + + }; + + var choices = this._data.choices; + for (var i = 0; i < choices.length; i++) { + renderChoice(choices[i]); + } + + bodyDiv[0].scrollTop = scrollTop; + } +}; + +ListFacet.prototype._select = function(choice, only) { + if (only) { + this._selection = []; + } + this._selection.push(choice); + this._updateRest(); +}; + +ListFacet.prototype._deselect = function(choice) { + for (var i = this._selection.length - 1; i >= 0; i--) { + if (this._selection[i] == choice) { + this._selection.splice(i, 1); + break; + } + } + this._updateRest(); +}; + +ListFacet.prototype._reset = function() { + this._selection = []; + this._updateRest(); +}; + +ListFacet.prototype._updateRest = function() { + ui.browsingEngine.update(); + ui.dataTableView.update(true); +}; \ No newline at end of file diff --git a/src/main/webapp/scripts/util/misc.js b/src/main/webapp/scripts/util/misc.js new file mode 100644 index 000000000..ea5047864 --- /dev/null +++ b/src/main/webapp/scripts/util/misc.js @@ -0,0 +1,23 @@ +function cloneDeep(o) { + if (o === undefined || o === null) { + return o; + } else if (o instanceof Function) { + return o; + } else if (o instanceof Array) { + var a = []; + for (var i = 0; i < o.length; i++) { + a.push(cloneDeep(o[i])); + } + return a; + } else if (o instanceof Object) { + var a = {}; + for (var n in o) { + if (o.hasOwnProperty(n)) { + a[n] = cloneDeep(o[n]); + } + } + return a; + } else { + return o; + } +} \ No newline at end of file diff --git a/src/main/webapp/styles/browsing.css b/src/main/webapp/styles/browsing.css new file mode 100644 index 000000000..e4da4e7d4 --- /dev/null +++ b/src/main/webapp/styles/browsing.css @@ -0,0 +1,59 @@ +.browsing-panel { +} + +.facet-container { + clear: both; + margin-top: 1em; +} + +.facet-title { + padding: 5px; + background: #eee; + font-weight: bold; +} +.facet-body { + border: 1px solid #ccc; + padding: 1px; + height: 20em; + overflow: auto; +} +.facet-choice { + padding: 2px 5px; + clear: both; +} +.facet-choice-selected .facet-choice-label { + font-weight: bold; +} + +a.facet-choice-label { + margin-right: 0.5em; + text-decoration: none; + color: #004; +} +a.facet-choice-label:hover { + text-decoration: underline; + color: #66f; +} + +.facet-choice-count { + color: #aaa; +} +.facet-choice-count:before { + content: "("; + color: #aaa; +} +.facet-choice-count:after { + content: ")"; + color: #aaa; +} + +a.facet-choice-link { + font-size: 80%; + float: right; + text-decoration: none; + color: #aac; +} +a.facet-choice-link:hover { + text-decoration: underline; + color: #88a; +} diff --git a/src/main/webapp/styles/history.css b/src/main/webapp/styles/history.css new file mode 100644 index 000000000..344beb0bc --- /dev/null +++ b/src/main/webapp/styles/history.css @@ -0,0 +1,35 @@ +.history-panel { + position: absolute; + top: -1px; + right: 20px; + width: 200px; + padding: 2px; + background: #fffee0; + border: 1px solid #ccc; + height: 100px; + overflow: auto; +} +.history-panel h3 { + margin: 0; + padding: 3px; + background: #fee; + font-size: 100%; +} +.history-past { + padding-bottom: 3px; + border-bottom: 2px solid #aaa; +} +.history-future { + padding-top: 3px; +} +a.history-entry { + display: block; + padding: 3px 5px; + border-bottom: 1px solid #eee; + text-decoration: none; + color: black; +} +a.history-entry:hover { + background: #eee; + color: #a88; +} diff --git a/src/main/webapp/styles/project.css b/src/main/webapp/styles/project.css index 05dfea5cd..294b7546e 100644 --- a/src/main/webapp/styles/project.css +++ b/src/main/webapp/styles/project.css @@ -33,38 +33,3 @@ img.column-header-menu { text-align: center; margin: 1em 0; } - -.history-panel { - position: absolute; - top: -1px; - right: 20px; - width: 200px; - padding: 2px; - background: white; - border: 1px solid #eee; - height: 200px; - overflow: auto; -} -.history-panel h3 { - margin: 0; - padding: 5px; - background: #fee; -} -.history-past { - padding-bottom: 3px; - border-bottom: 2px solid #aaa; -} -.history-future { - padding-top: 3px; -} -a.history-entry { - display: block; - padding: 3px 5px; - border-bottom: 1px solid #eee; - text-decoration: none; - color: black; -} -a.history-entry:hover { - background: #eee; - color: #a88; -}