From 57978c324b1f84019ecc86f9c1fed66bdb1ebbc6 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Tue, 28 Feb 2012 22:58:20 +0000 Subject: [PATCH] [docs,build][m]: build latest version of the docs doing some tidying of the docs along the way. --- docs/backend.html | 353 ------------------------------ docs/backend/base.html | 37 ++++ docs/backend/dataproxy.html | 87 ++++++++ docs/backend/docco.css | 186 ++++++++++++++++ docs/backend/elasticsearch.html | 105 +++++++++ docs/backend/gdocs.html | 98 +++++++++ docs/backend/memory.html | 98 +++++++++ docs/backend/webstore.html | 61 ++++++ docs/model.html | 7 +- docs/view-flot-graph.html | 4 +- docs/view-grid.html | 315 +++++++++++++++++++++++++++ docs/view.html | 367 +++++++------------------------- index.html | 7 +- src/backend/base.js | 4 +- src/backend/elasticsearch.js | 17 +- src/backend/memory.js | 7 +- src/view-grid.js | 21 +- 17 files changed, 1105 insertions(+), 669 deletions(-) delete mode 100644 docs/backend.html create mode 100644 docs/backend/base.html create mode 100644 docs/backend/dataproxy.html create mode 100644 docs/backend/docco.css create mode 100644 docs/backend/elasticsearch.html create mode 100644 docs/backend/gdocs.html create mode 100644 docs/backend/memory.html create mode 100644 docs/backend/webstore.html create mode 100644 docs/view-grid.html diff --git a/docs/backend.html b/docs/backend.html deleted file mode 100644 index b1f5009e..00000000 --- a/docs/backend.html +++ /dev/null @@ -1,353 +0,0 @@ - backend.js

backend.js

Recline Backends

- -

Backends are connectors to backend data sources and stores

- -

Backends are implemented as Backbone models but this is just a -convenience (they do not save or load themselves from any remote -source)

this.recline = this.recline || {};
-this.recline.Model = this.recline.Model || {};
-
-(function($, my) {

Backbone.sync

- -

Override Backbone.sync to hand off to sync function in relevant backend

  Backbone.sync = function(method, model, options) {
-    return model.backend.sync(method, model, options);
-  }

wrapInTimeout

- -

Crude way to catch backend errors -Many of backends use JSONP and so will not get error messages and this is -a crude way to catch those errors.

  function wrapInTimeout(ourFunction) {
-    var dfd = $.Deferred();
-    var timeout = 5000;
-    var timer = setTimeout(function() {
-      dfd.reject({
-        message: 'Request Error: Backend did not respond after ' + (timeout / 1000) + ' seconds'
-      });
-    }, timeout);
-    ourFunction.done(function(arguments) {
-        clearTimeout(timer);
-        dfd.resolve(arguments);
-      })
-      .fail(function(arguments) {
-        clearTimeout(timer);
-        dfd.reject(arguments);
-      })
-      ;
-    return dfd.promise();
-  }

BackendMemory - uses in-memory data

- -

This is very artificial and is really only designed for testing -purposes.

- -

To use it you should provide in your constructor data:

- -
    -
  • metadata (including fields array)
  • -
  • documents: list of hashes, each hash being one doc. A doc must have an id attribute which is unique.

    - -

    Example:

    - -
    -// Backend setup
    -var backend = Backend();
    -backend.addDataset({
    -metadata: {
    - id: 'my-id',
    - title: 'My Title'
    -},
    -fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}],
    -documents: [
    -   {id: 0, x: 1, y: 2, z: 3},
    -   {id: 1, x: 2, y: 4, z: 6}
    - ]
    -});
    -// later ...
    -var dataset = Dataset({id: 'my-id'});
    -dataset.fetch();
    -etc ...
    -
  • -
  my.BackendMemory = Backbone.Model.extend({
-    initialize: function() {
-      this.datasets = {};
-    },
-    addDataset: function(data) {
-      this.datasets[data.metadata.id] = $.extend(true, {}, data);
-    },
-    sync: function(method, model, options) {
-      var self = this;
-      if (method === "read") {
-        var dfd = $.Deferred();
-        if (model.__type__ == 'Dataset') {
-          var rawDataset = this.datasets[model.id];
-          model.set(rawDataset.metadata);
-          model.fields.reset(rawDataset.fields);
-          model.docCount = rawDataset.documents.length;
-          dfd.resolve(model);
-        }
-        return dfd.promise();
-      } else if (method === 'update') {
-        var dfd = $.Deferred();
-        if (model.__type__ == 'Document') {
-          _.each(self.datasets[model.dataset.id].documents, function(doc, idx) {
-            if(doc.id === model.id) {
-              self.datasets[model.dataset.id].documents[idx] = model.toJSON();
-            }
-          });
-          dfd.resolve(model);
-        }
-        return dfd.promise();
-      } else if (method === 'delete') {
-        var dfd = $.Deferred();
-        if (model.__type__ == 'Document') {
-          var rawDataset = self.datasets[model.dataset.id];
-          var newdocs = _.reject(rawDataset.documents, function(doc) {
-            return (doc.id === model.id);
-          });
-          rawDataset.documents = newdocs;
-          dfd.resolve(model);
-        }
-        return dfd.promise();
-      } else {
-        alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model);
-      }
-    },
-    query: function(model, queryObj) {
-      var numRows = queryObj.size;
-      var start = queryObj.offset;
-      var dfd = $.Deferred();
-      results = this.datasets[model.id].documents;

not complete sorting!

      _.each(queryObj.sort, function(item) {
-        results = _.sortBy(results, function(doc) {
-          var _out = doc[item[0]];
-          return (item[1] == 'asc') ? _out : -1*_out;
-        });
-      });
-      var results = results.slice(start, start+numRows);
-      dfd.resolve(results);
-      return dfd.promise();
-    }
-  });
-  my.backends['memory'] = new my.BackendMemory();

BackendWebstore

- -

Connecting to Webstores

- -

To use this backend ensure your Dataset has a webstore_url in its attributes.

  my.BackendWebstore = Backbone.Model.extend({
-    sync: function(method, model, options) {
-      if (method === "read") {
-        if (model.__type__ == 'Dataset') {
-          var base = model.get('webstore_url');
-          var schemaUrl = base + '/schema.json';
-          var jqxhr = $.ajax({
-            url: schemaUrl,
-              dataType: 'jsonp',
-              jsonp: '_callback'
-          });
-          var dfd = $.Deferred();
-          wrapInTimeout(jqxhr).done(function(schema) {
-            var fieldData = _.map(schema.data, function(item) {
-              item.id = item.name;
-              delete item.name;
-              return item;
-            });
-            model.fields.reset(fieldData);
-            model.docCount = schema.count;
-            dfd.resolve(model, jqxhr);
-          })
-          .fail(function(arguments) {
-            dfd.reject(arguments);
-          });
-          return dfd.promise();
-        }
-      }
-    },
-    query: function(model, queryObj) {
-      var base = model.get('webstore_url');
-      var data = {
-        _limit:  queryObj.size
-        , _offset: queryObj.offset
-      };
-      var jqxhr = $.ajax({
-        url: base + '.json',
-        data: data,
-        dataType: 'jsonp',
-        jsonp: '_callback',
-        cache: true
-      });
-      var dfd = $.Deferred();
-      jqxhr.done(function(results) {
-        dfd.resolve(results.data);
-      });
-      return dfd.promise();
-    }
-  });
-  my.backends['webstore'] = new my.BackendWebstore();

BackendDataProxy

- -

For connecting to DataProxy-s.

- -

When initializing the DataProxy backend you can set the following attributes:

- -
    -
  • dataproxy: {url-to-proxy} (optional). Defaults to http://jsonpdataproxy.appspot.com
  • -
- -

Datasets using using this backend should set the following attributes:

- -
    -
  • url: (required) url-of-data-to-proxy
  • -
  • format: (optional) csv | xls (defaults to csv if not specified)
  • -
- -

Note that this is a read-only backend.

  my.BackendDataProxy = Backbone.Model.extend({
-    defaults: {
-      dataproxy_url: 'http://jsonpdataproxy.appspot.com'
-    },
-    sync: function(method, model, options) {
-      var self = this;
-      if (method === "read") {
-        if (model.__type__ == 'Dataset') {
-          var base = self.get('dataproxy_url');

TODO: should we cache for extra efficiency

          var data = {
-            url: model.get('url')
-            , 'max-results':  1
-            , type: model.get('format') || 'csv'
-          };
-          var jqxhr = $.ajax({
-            url: base
-            , data: data
-            , dataType: 'jsonp'
-          });
-          var dfd = $.Deferred();
-          wrapInTimeout(jqxhr).done(function(results) {
-            model.fields.reset(_.map(results.fields, function(fieldId) {
-              return {id: fieldId};
-              })
-            );
-            dfd.resolve(model, jqxhr);
-          })
-          .fail(function(arguments) {
-            dfd.reject(arguments);
-          });
-          return dfd.promise();
-        }
-      } else {
-        alert('This backend only supports read operations');
-      }
-    },
-    query: function(dataset, queryObj) {
-      var base = this.get('dataproxy_url');
-      var data = {
-        url: dataset.get('url')
-        , 'max-results':  queryObj.size
-        , type: dataset.get('format')
-      };
-      var jqxhr = $.ajax({
-        url: base
-        , data: data
-        , dataType: 'jsonp'
-      });
-      var dfd = $.Deferred();
-      jqxhr.done(function(results) {
-        var _out = _.map(results.data, function(doc) {
-          var tmp = {};
-          _.each(results.fields, function(key, idx) {
-            tmp[key] = doc[idx];
-          });
-          return tmp;
-        });
-        dfd.resolve(_out);
-      });
-      return dfd.promise();
-    }
-  });
-  my.backends['dataproxy'] = new my.BackendDataProxy();

Google spreadsheet backend

- -

Connect to Google Docs spreadsheet.

- -

Dataset must have a url attribute pointing to the Gdocs -spreadsheet's JSON feed e.g.

- -
-var dataset = new recline.Model.Dataset({
-    url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json'
-  },
-  'gdocs'
-);
-
  my.BackendGDoc = Backbone.Model.extend({
-    sync: function(method, model, options) {
-      var self = this;
-      if (method === "read") { 
-        var dfd = $.Deferred(); 
-        var dataset = model;
-
-        $.getJSON(model.get('url'), function(d) {
-          result = self.gdocsToJavascript(d);
-          model.fields.reset(_.map(result.field, function(fieldId) {
-              return {id: fieldId};
-            })
-          );

cache data onto dataset (we have loaded whole gdoc it seems!)

          model._dataCache = result.data;
-          dfd.resolve(model);
-        })
-        return dfd.promise(); }
-    },
-
-    query: function(dataset, queryObj) { 
-      var dfd = $.Deferred();
-      var fields = _.pluck(dataset.fields.toJSON(), 'id');

zip the fields with the data rows to produce js objs -TODO: factor this out as a common method with other backends

      var objs = _.map(dataset._dataCache, function (d) { 
-        var obj = {};
-        _.each(_.zip(fields, d), function (x) { obj[x[0]] = x[1]; })
-        return obj;
-      });
-      dfd.resolve(objs);
-      return dfd;
-    },
-    gdocsToJavascript:  function(gdocsSpreadsheet) {
-      /*
-         :options: (optional) optional argument dictionary:
-         columnsToUse: list of columns to use (specified by field names)
-         colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion).
-         :return: tabular data object (hash with keys: field and data).
-
-         Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.
-         */
-      var options = {};
-      if (arguments.length > 1) {
-        options = arguments[1];
-      }
-      var results = {
-        'field': [],
-        'data': []
-      };

default is no special info on type of columns

      var colTypes = {};
-      if (options.colTypes) {
-        colTypes = options.colTypes;
-      }

either extract column headings from spreadsheet directly, or used supplied ones

      if (options.columnsToUse) {

columns set to subset supplied

        results.field = options.columnsToUse;
-      } else {

set columns to use to be all available

        if (gdocsSpreadsheet.feed.entry.length > 0) {
-          for (var k in gdocsSpreadsheet.feed.entry[0]) {
-            if (k.substr(0, 3) == 'gsx') {
-              var col = k.substr(4)
-                results.field.push(col);
-            }
-          }
-        }
-      }

converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])

      var rep = /^([\d\.\-]+)\%$/;
-      $.each(gdocsSpreadsheet.feed.entry, function (i, entry) {
-        var row = [];
-        for (var k in results.field) {
-          var col = results.field[k];
-          var _keyname = 'gsx$' + col;
-          var value = entry[_keyname]['$t'];

if labelled as % and value contains %, convert

          if (colTypes[col] == 'percent') {
-            if (rep.test(value)) {
-              var value2 = rep.exec(value);
-              var value3 = parseFloat(value2);
-              value = value3 / 100;
-            }
-          }
-          row.push(value);
-        }
-        results.data.push(row);
-      });
-      return results;
-    }
-  });
-  my.backends['gdocs'] = new my.BackendGDoc();
-
-}(jQuery, this.recline.Model));
-
-
\ No newline at end of file diff --git a/docs/backend/base.html b/docs/backend/base.html new file mode 100644 index 00000000..92c0ca53 --- /dev/null +++ b/docs/backend/base.html @@ -0,0 +1,37 @@ + base.js

base.js

Recline Backends

+ +

Backends are connectors to backend data sources and stores

+ +

This is just the base module containing various convenience methods.

this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+
+(function($, my) {

Backbone.sync

+ +

Override Backbone.sync to hand off to sync function in relevant backend

  Backbone.sync = function(method, model, options) {
+    return model.backend.sync(method, model, options);
+  }

wrapInTimeout

+ +

Crude way to catch backend errors +Many of backends use JSONP and so will not get error messages and this is +a crude way to catch those errors.

  my.wrapInTimeout = function(ourFunction) {
+    var dfd = $.Deferred();
+    var timeout = 5000;
+    var timer = setTimeout(function() {
+      dfd.reject({
+        message: 'Request Error: Backend did not respond after ' + (timeout / 1000) + ' seconds'
+      });
+    }, timeout);
+    ourFunction.done(function(arguments) {
+        clearTimeout(timer);
+        dfd.resolve(arguments);
+      })
+      .fail(function(arguments) {
+        clearTimeout(timer);
+        dfd.reject(arguments);
+      })
+      ;
+    return dfd.promise();
+  }
+}(jQuery, this.recline.Backend));
+
+
\ No newline at end of file diff --git a/docs/backend/dataproxy.html b/docs/backend/dataproxy.html new file mode 100644 index 00000000..83405602 --- /dev/null +++ b/docs/backend/dataproxy.html @@ -0,0 +1,87 @@ + dataproxy.js

dataproxy.js

this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+
+(function($, my) {

DataProxy Backend

+ +

For connecting to DataProxy-s.

+ +

When initializing the DataProxy backend you can set the following attributes:

+ +
    +
  • dataproxy: {url-to-proxy} (optional). Defaults to http://jsonpdataproxy.appspot.com
  • +
+ +

Datasets using using this backend should set the following attributes:

+ +
    +
  • url: (required) url-of-data-to-proxy
  • +
  • format: (optional) csv | xls (defaults to csv if not specified)
  • +
+ +

Note that this is a read-only backend.

  my.DataProxy = Backbone.Model.extend({
+    defaults: {
+      dataproxy_url: 'http://jsonpdataproxy.appspot.com'
+    },
+    sync: function(method, model, options) {
+      var self = this;
+      if (method === "read") {
+        if (model.__type__ == 'Dataset') {
+          var base = self.get('dataproxy_url');

TODO: should we cache for extra efficiency

          var data = {
+            url: model.get('url')
+            , 'max-results':  1
+            , type: model.get('format') || 'csv'
+          };
+          var jqxhr = $.ajax({
+            url: base
+            , data: data
+            , dataType: 'jsonp'
+          });
+          var dfd = $.Deferred();
+          my.wrapInTimeout(jqxhr).done(function(results) {
+            model.fields.reset(_.map(results.fields, function(fieldId) {
+              return {id: fieldId};
+              })
+            );
+            dfd.resolve(model, jqxhr);
+          })
+          .fail(function(arguments) {
+            dfd.reject(arguments);
+          });
+          return dfd.promise();
+        }
+      } else {
+        alert('This backend only supports read operations');
+      }
+    },
+    query: function(dataset, queryObj) {
+      var base = this.get('dataproxy_url');
+      var data = {
+        url: dataset.get('url')
+        , 'max-results':  queryObj.size
+        , type: dataset.get('format')
+      };
+      var jqxhr = $.ajax({
+        url: base
+        , data: data
+        , dataType: 'jsonp'
+      });
+      var dfd = $.Deferred();
+      jqxhr.done(function(results) {
+        var _out = _.map(results.data, function(doc) {
+          var tmp = {};
+          _.each(results.fields, function(key, idx) {
+            tmp[key] = doc[idx];
+          });
+          return tmp;
+        });
+        dfd.resolve(_out);
+      });
+      return dfd.promise();
+    }
+  });
+  recline.Model.backends['dataproxy'] = new my.DataProxy();
+
+
+}(jQuery, this.recline.Backend));
+
+
\ No newline at end of file diff --git a/docs/backend/docco.css b/docs/backend/docco.css new file mode 100644 index 00000000..5aa0a8d7 --- /dev/null +++ b/docs/backend/docco.css @@ -0,0 +1,186 @@ +/*--------------------- Layout and Typography ----------------------------*/ +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + color: #252519; + margin: 0; padding: 0; +} +a { + color: #261a3b; +} + a:visited { + color: #261a3b; + } +p { + margin: 0 0 15px 0; +} +h1, h2, h3, h4, h5, h6 { + margin: 0px 0 15px 0; +} + h1 { + margin-top: 40px; + } +#container { + position: relative; +} +#background { + position: fixed; + top: 0; left: 525px; right: 0; bottom: 0; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + z-index: -1; +} +#jump_to, #jump_page { + background: white; + -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; + -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; + font: 10px Arial; + text-transform: uppercase; + cursor: pointer; + text-align: right; +} +#jump_to, #jump_wrapper { + position: fixed; + right: 0; top: 0; + padding: 5px 10px; +} + #jump_wrapper { + padding: 0; + display: none; + } + #jump_to:hover #jump_wrapper { + display: block; + } + #jump_page { + padding: 5px 0 3px; + margin: 0 0 25px 25px; + } + #jump_page .source { + display: block; + padding: 5px 10px; + text-decoration: none; + border-top: 1px solid #eee; + } + #jump_page .source:hover { + background: #f5f5ff; + } + #jump_page .source:first-child { + } +table td { + border: 0; + outline: 0; +} + td.docs, th.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; + } + .docs pre { + margin: 15px 0 15px; + padding-left: 15px; + } + .docs p tt, .docs p code { + background: #f8f8ff; + border: 1px solid #dedede; + font-size: 12px; + padding: 0 0.2em; + } + .pilwrap { + position: relative; + } + .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + top: 3px; left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + } + td.docs:hover .pilcrow { + opacity: 1; + } + td.code, th.code { + padding: 14px 15px 16px 25px; + width: 100%; + vertical-align: top; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + } + pre, tt, code { + font-size: 12px; line-height: 18px; + font-family: Monaco, Consolas, "Lucida Console", monospace; + margin: 0; padding: 0; + } + + +/*---------------------- Syntax Highlighting -----------------------------*/ +td.linenos { background-color: #f0f0f0; padding-right: 10px; } +span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } +body .hll { background-color: #ffffcc } +body .c { color: #408080; font-style: italic } /* Comment */ +body .err { border: 1px solid #FF0000 } /* Error */ +body .k { color: #954121 } /* Keyword */ +body .o { color: #666666 } /* Operator */ +body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +body .cp { color: #BC7A00 } /* Comment.Preproc */ +body .c1 { color: #408080; font-style: italic } /* Comment.Single */ +body .cs { color: #408080; font-style: italic } /* Comment.Special */ +body .gd { color: #A00000 } /* Generic.Deleted */ +body .ge { font-style: italic } /* Generic.Emph */ +body .gr { color: #FF0000 } /* Generic.Error */ +body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +body .gi { color: #00A000 } /* Generic.Inserted */ +body .go { color: #808080 } /* Generic.Output */ +body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +body .gs { font-weight: bold } /* Generic.Strong */ +body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +body .gt { color: #0040D0 } /* Generic.Traceback */ +body .kc { color: #954121 } /* Keyword.Constant */ +body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ +body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ +body .kp { color: #954121 } /* Keyword.Pseudo */ +body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ +body .kt { color: #B00040 } /* Keyword.Type */ +body .m { color: #666666 } /* Literal.Number */ +body .s { color: #219161 } /* Literal.String */ +body .na { color: #7D9029 } /* Name.Attribute */ +body .nb { color: #954121 } /* Name.Builtin */ +body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +body .no { color: #880000 } /* Name.Constant */ +body .nd { color: #AA22FF } /* Name.Decorator */ +body .ni { color: #999999; font-weight: bold } /* Name.Entity */ +body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +body .nf { color: #0000FF } /* Name.Function */ +body .nl { color: #A0A000 } /* Name.Label */ +body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +body .nt { color: #954121; font-weight: bold } /* Name.Tag */ +body .nv { color: #19469D } /* Name.Variable */ +body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +body .w { color: #bbbbbb } /* Text.Whitespace */ +body .mf { color: #666666 } /* Literal.Number.Float */ +body .mh { color: #666666 } /* Literal.Number.Hex */ +body .mi { color: #666666 } /* Literal.Number.Integer */ +body .mo { color: #666666 } /* Literal.Number.Oct */ +body .sb { color: #219161 } /* Literal.String.Backtick */ +body .sc { color: #219161 } /* Literal.String.Char */ +body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ +body .s2 { color: #219161 } /* Literal.String.Double */ +body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +body .sh { color: #219161 } /* Literal.String.Heredoc */ +body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +body .sx { color: #954121 } /* Literal.String.Other */ +body .sr { color: #BB6688 } /* Literal.String.Regex */ +body .s1 { color: #219161 } /* Literal.String.Single */ +body .ss { color: #19469D } /* Literal.String.Symbol */ +body .bp { color: #954121 } /* Name.Builtin.Pseudo */ +body .vc { color: #19469D } /* Name.Variable.Class */ +body .vg { color: #19469D } /* Name.Variable.Global */ +body .vi { color: #19469D } /* Name.Variable.Instance */ +body .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/backend/elasticsearch.html b/docs/backend/elasticsearch.html new file mode 100644 index 00000000..d576b1cf --- /dev/null +++ b/docs/backend/elasticsearch.html @@ -0,0 +1,105 @@ + elasticsearch.js

elasticsearch.js

this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+
+(function($, my) {

ElasticSearch Backend

+ +

Connecting to ElasticSearch.

+ +

To use this backend ensure your Dataset has one of the following +attributes (first one found is used):

+ +
+elasticsearch_url
+webstore_url
+url
+
+ +

This should point to the ES type url. E.G. for ES running on +localhost:9200 with index twitter and type tweet it would be

+ +
http://localhost:9200/twitter/tweet
  my.ElasticSearch = Backbone.Model.extend({
+    _getESUrl: function(dataset) {
+      var out = dataset.get('elasticsearch_url');
+      if (out) return out;
+      out = dataset.get('webstore_url');
+      if (out) return out;
+      out = dataset.get('url');
+      return out;
+    },
+    sync: function(method, model, options) {
+      var self = this;
+      if (method === "read") {
+        if (model.__type__ == 'Dataset') {
+          var base = self._getESUrl(model);
+          var schemaUrl = base + '/_mapping';
+          var jqxhr = $.ajax({
+            url: schemaUrl,
+            dataType: 'jsonp'
+          });
+          var dfd = $.Deferred();
+          my.wrapInTimeout(jqxhr).done(function(schema) {

only one top level key in ES = the type so we can ignore it

            var key = _.keys(schema)[0];
+            var fieldData = _.map(schema[key].properties, function(dict, fieldName) {
+              dict.id = fieldName;
+              return dict;
+            });
+            model.fields.reset(fieldData);
+            dfd.resolve(model, jqxhr);
+          })
+          .fail(function(arguments) {
+            dfd.reject(arguments);
+          });
+          return dfd.promise();
+        }
+      } else {
+        alert('This backend currently only supports read operations');
+      }
+    },
+    _normalizeQuery: function(queryObj) {
+      if (queryObj.toJSON) {
+        var out = queryObj.toJSON();
+      } else {
+        var out = _.extend({}, queryObj);
+      }
+      if (out.q != undefined && out.q.trim() === '') {
+        delete out.q;
+      }
+      if (!out.q) {
+        out.query = {
+          match_all: {}
+        }
+      } else {
+        out.query = {
+          query_string: {
+            query: out.q
+          }
+        }
+        delete out.q;
+      }
+      return out;
+    },
+    query: function(model, queryObj) {
+      var queryNormalized = this._normalizeQuery(queryObj);
+      var data = {source: JSON.stringify(queryNormalized)};
+      var base = this._getESUrl(model);
+      var jqxhr = $.ajax({
+        url: base + '/_search',
+        data: data,
+        dataType: 'jsonp'
+      });
+      var dfd = $.Deferred();

TODO: fail case

      jqxhr.done(function(results) {
+        model.docCount = results.hits.total;
+        var docs = _.map(results.hits.hits, function(result) {
+          var _out = result._source;
+          _out.id = result._id;
+          return _out;
+        });
+        dfd.resolve(docs);
+      });
+      return dfd.promise();
+    }
+  });
+  recline.Model.backends['elasticsearch'] = new my.ElasticSearch();
+
+}(jQuery, this.recline.Backend));
+
+
\ No newline at end of file diff --git a/docs/backend/gdocs.html b/docs/backend/gdocs.html new file mode 100644 index 00000000..d503f679 --- /dev/null +++ b/docs/backend/gdocs.html @@ -0,0 +1,98 @@ + gdocs.js

gdocs.js

this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+
+(function($, my) {

Google spreadsheet backend

+ +

Connect to Google Docs spreadsheet.

+ +

Dataset must have a url attribute pointing to the Gdocs +spreadsheet's JSON feed e.g.

+ +
+var dataset = new recline.Model.Dataset({
+    url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json'
+  },
+  'gdocs'
+);
+
  my.GDoc = Backbone.Model.extend({
+    sync: function(method, model, options) {
+      var self = this;
+      if (method === "read") { 
+        var dfd = $.Deferred(); 
+        var dataset = model;
+
+        $.getJSON(model.get('url'), function(d) {
+          result = self.gdocsToJavascript(d);
+          model.fields.reset(_.map(result.field, function(fieldId) {
+              return {id: fieldId};
+            })
+          );

cache data onto dataset (we have loaded whole gdoc it seems!)

          model._dataCache = result.data;
+          dfd.resolve(model);
+        })
+        return dfd.promise(); }
+    },
+
+    query: function(dataset, queryObj) { 
+      var dfd = $.Deferred();
+      var fields = _.pluck(dataset.fields.toJSON(), 'id');

zip the fields with the data rows to produce js objs +TODO: factor this out as a common method with other backends

      var objs = _.map(dataset._dataCache, function (d) { 
+        var obj = {};
+        _.each(_.zip(fields, d), function (x) { obj[x[0]] = x[1]; })
+        return obj;
+      });
+      dfd.resolve(objs);
+      return dfd;
+    },
+    gdocsToJavascript:  function(gdocsSpreadsheet) {
+      /*
+         :options: (optional) optional argument dictionary:
+         columnsToUse: list of columns to use (specified by field names)
+         colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion).
+         :return: tabular data object (hash with keys: field and data).
+
+         Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.
+         */
+      var options = {};
+      if (arguments.length > 1) {
+        options = arguments[1];
+      }
+      var results = {
+        'field': [],
+        'data': []
+      };

default is no special info on type of columns

      var colTypes = {};
+      if (options.colTypes) {
+        colTypes = options.colTypes;
+      }

either extract column headings from spreadsheet directly, or used supplied ones

      if (options.columnsToUse) {

columns set to subset supplied

        results.field = options.columnsToUse;
+      } else {

set columns to use to be all available

        if (gdocsSpreadsheet.feed.entry.length > 0) {
+          for (var k in gdocsSpreadsheet.feed.entry[0]) {
+            if (k.substr(0, 3) == 'gsx') {
+              var col = k.substr(4)
+                results.field.push(col);
+            }
+          }
+        }
+      }

converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])

      var rep = /^([\d\.\-]+)\%$/;
+      $.each(gdocsSpreadsheet.feed.entry, function (i, entry) {
+        var row = [];
+        for (var k in results.field) {
+          var col = results.field[k];
+          var _keyname = 'gsx$' + col;
+          var value = entry[_keyname]['$t'];

if labelled as % and value contains %, convert

          if (colTypes[col] == 'percent') {
+            if (rep.test(value)) {
+              var value2 = rep.exec(value);
+              var value3 = parseFloat(value2);
+              value = value3 / 100;
+            }
+          }
+          row.push(value);
+        }
+        results.data.push(row);
+      });
+      return results;
+    }
+  });
+  recline.Model.backends['gdocs'] = new my.GDoc();
+
+}(jQuery, this.recline.Backend));
+
+
\ No newline at end of file diff --git a/docs/backend/memory.html b/docs/backend/memory.html new file mode 100644 index 00000000..982be977 --- /dev/null +++ b/docs/backend/memory.html @@ -0,0 +1,98 @@ + memory.js

memory.js

this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+
+(function($, my) {

Memory Backend - uses in-memory data

+ +

To use it you should provide in your constructor data:

+ +
    +
  • metadata (including fields array)
  • +
  • documents: list of hashes, each hash being one doc. A doc must have an id attribute which is unique.
  • +
+ +

Example:

+ +

+ // Backend setup
+ var backend = recline.Backend.Memory();
+ backend.addDataset({
+   metadata: {
+     id: 'my-id',
+     title: 'My Title'
+   },
+   fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}],
+   documents: [
+       {id: 0, x: 1, y: 2, z: 3},
+       {id: 1, x: 2, y: 4, z: 6}
+     ]
+ });
+ // later ...
+ var dataset = Dataset({id: 'my-id'}, 'memory');
+ dataset.fetch();
+ etc ...
+ 

  my.Memory = Backbone.Model.extend({
+    initialize: function() {
+      this.datasets = {};
+    },
+    addDataset: function(data) {
+      this.datasets[data.metadata.id] = $.extend(true, {}, data);
+    },
+    sync: function(method, model, options) {
+      var self = this;
+      if (method === "read") {
+        var dfd = $.Deferred();
+        if (model.__type__ == 'Dataset') {
+          var rawDataset = this.datasets[model.id];
+          model.set(rawDataset.metadata);
+          model.fields.reset(rawDataset.fields);
+          model.docCount = rawDataset.documents.length;
+          dfd.resolve(model);
+        }
+        return dfd.promise();
+      } else if (method === 'update') {
+        var dfd = $.Deferred();
+        if (model.__type__ == 'Document') {
+          _.each(self.datasets[model.dataset.id].documents, function(doc, idx) {
+            if(doc.id === model.id) {
+              self.datasets[model.dataset.id].documents[idx] = model.toJSON();
+            }
+          });
+          dfd.resolve(model);
+        }
+        return dfd.promise();
+      } else if (method === 'delete') {
+        var dfd = $.Deferred();
+        if (model.__type__ == 'Document') {
+          var rawDataset = self.datasets[model.dataset.id];
+          var newdocs = _.reject(rawDataset.documents, function(doc) {
+            return (doc.id === model.id);
+          });
+          rawDataset.documents = newdocs;
+          dfd.resolve(model);
+        }
+        return dfd.promise();
+      } else {
+        alert('Not supported: sync on Memory backend with method ' + method + ' and model ' + model);
+      }
+    },
+    query: function(model, queryObj) {
+      var numRows = queryObj.size;
+      var start = queryObj.from;
+      var dfd = $.Deferred();
+      results = this.datasets[model.id].documents;

not complete sorting!

      _.each(queryObj.sort, function(sortObj) {
+        var fieldName = _.keys(sortObj)[0];
+        results = _.sortBy(results, function(doc) {
+          var _out = doc[fieldName];
+          return (sortObj[fieldName].order == 'asc') ? _out : -1*_out;
+        });
+      });
+      var results = results.slice(start, start+numRows);
+      dfd.resolve(results);
+      return dfd.promise();
+    }
+  });
+  recline.Model.backends['memory'] = new my.Memory();
+
+}(jQuery, this.recline.Backend));
+
+
\ No newline at end of file diff --git a/docs/backend/webstore.html b/docs/backend/webstore.html new file mode 100644 index 00000000..aae1bbc5 --- /dev/null +++ b/docs/backend/webstore.html @@ -0,0 +1,61 @@ + webstore.js

webstore.js

this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+
+(function($, my) {

Webstore Backend

+ +

Connecting to Webstores

+ +

To use this backend ensure your Dataset has a webstore_url in its attributes.

  my.Webstore = Backbone.Model.extend({
+    sync: function(method, model, options) {
+      if (method === "read") {
+        if (model.__type__ == 'Dataset') {
+          var base = model.get('webstore_url');
+          var schemaUrl = base + '/schema.json';
+          var jqxhr = $.ajax({
+            url: schemaUrl,
+              dataType: 'jsonp',
+              jsonp: '_callback'
+          });
+          var dfd = $.Deferred();
+          my.wrapInTimeout(jqxhr).done(function(schema) {
+            var fieldData = _.map(schema.data, function(item) {
+              item.id = item.name;
+              delete item.name;
+              return item;
+            });
+            model.fields.reset(fieldData);
+            model.docCount = schema.count;
+            dfd.resolve(model, jqxhr);
+          })
+          .fail(function(arguments) {
+            dfd.reject(arguments);
+          });
+          return dfd.promise();
+        }
+      }
+    },
+    query: function(model, queryObj) {
+      var base = model.get('webstore_url');
+      var data = {
+        _limit:  queryObj.size
+        , _offset: queryObj.from
+      };
+      var jqxhr = $.ajax({
+        url: base + '.json',
+        data: data,
+        dataType: 'jsonp',
+        jsonp: '_callback',
+        cache: true
+      });
+      var dfd = $.Deferred();
+      jqxhr.done(function(results) {
+        dfd.resolve(results.data);
+      });
+      return dfd.promise();
+    }
+  });
+  recline.Model.backends['webstore'] = new my.Webstore();
+
+}(jQuery, this.recline.Backend));
+
+
\ No newline at end of file diff --git a/docs/model.html b/docs/model.html index cabde51b..053d7577 100644 --- a/docs/model.html +++ b/docs/model.html @@ -1,4 +1,4 @@ - model.js

model.js

Recline Backbone Models

this.recline = this.recline || {};
+      model.js           
});

model.js

Recline Backbone Models

this.recline = this.recline || {};
 this.recline.Model = this.recline.Model || {};
 
 (function($, my) {

A Dataset model

@@ -34,6 +34,7 @@ updated by queryObj (if provided).

Resulting DocumentList are used to reset this.currentDocuments and are also returned.

  query: function(queryObj) {
+    this.trigger('query:start');
     var self = this;
     this.queryState.set(queryObj, {silent: true});
     var dfd = $.Deferred();
@@ -45,9 +46,11 @@ also returned.

return _doc; }); self.currentDocuments.reset(docs); + self.trigger('query:done'); dfd.resolve(self.currentDocuments); }) .fail(function(arguments) { + self.trigger('query:fail', arguments); dfd.reject(arguments); }); return dfd.promise(); @@ -94,7 +97,7 @@ just pass a single argument representing id to the ctor

A Query object storing Dataset Query state

my.Query = Backbone.Model.extend({
   defaults: {
     size: 100
-    , offset: 0
+    , from: 0
   }
 });

Backend registry

diff --git a/docs/view-flot-graph.html b/docs/view-flot-graph.html index aa18c0b8..dac85e8a 100644 --- a/docs/view-flot-graph.html +++ b/docs/view-flot-graph.html @@ -1,4 +1,4 @@ - view-flot-graph.js

view-flot-graph.js

this.recline = this.recline || {};
+      view-flot-graph.js           

view-flot-graph.js

this.recline = this.recline || {};
 this.recline.View = this.recline.View || {};
 
 (function($, my) {

Graph view for a Dataset using Flot graphing library.

@@ -126,7 +126,7 @@ TODO: make this less invasive (e.g. preserve other keys in query string)

Uncaught Invalid dimensions for plot, width = 0, height = 0

  • There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value'
  •     var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]);
    -    if (!this.plot && (!areWeVisible || this.model.currentDocuments.length == 0)) {
    +    if ((!areWeVisible || this.model.currentDocuments.length == 0)) {
           return
         }

    create this.plot and cache it

        if (!this.plot) {

    only lines for the present

          options = {
             id: 'line',
    diff --git a/docs/view-grid.html b/docs/view-grid.html
    new file mode 100644
    index 00000000..c4c935d5
    --- /dev/null
    +++ b/docs/view-grid.html
    @@ -0,0 +1,315 @@
    +      view-grid.js           

    view-grid.js

    this.recline = this.recline || {};
    +this.recline.View = this.recline.View || {};
    +
    +(function($, my) {

    DataGrid

    + +

    Provides a tabular view on a Dataset.

    + +

    Initialize it with a recline.Dataset object.

    + +

    Additional options passed in second arguments. Options:

    + +
      +
    • cellRenderer: function used to render individual cells. See DataGridRow for more.
    • +
    my.DataGrid = Backbone.View.extend({
    +  tagName:  "div",
    +  className: "data-table-container",
    +
    +  initialize: function(modelEtc, options) {
    +    var self = this;
    +    this.el = $(this.el);
    +    _.bindAll(this, 'render');
    +    this.model.currentDocuments.bind('add', this.render);
    +    this.model.currentDocuments.bind('reset', this.render);
    +    this.model.currentDocuments.bind('remove', this.render);
    +    this.state = {};
    +    this.hiddenFields = [];
    +    this.options = options;
    +  },
    +
    +  events: {
    +    'click .column-header-menu': 'onColumnHeaderClick'
    +    , 'click .row-header-menu': 'onRowHeaderClick'
    +    , 'click .root-header-menu': 'onRootHeaderClick'
    +    , 'click .data-table-menu li a': 'onMenuClick'
    +  },

    TODO: delete or re-enable (currently this code is not used from anywhere except deprecated or disabled methods (see above)). +showDialog: function(template, data) { + if (!data) data = {}; + util.show('dialog'); + util.render(template, 'dialog-content', data); + util.observeExit($('.dialog-content'), function() { + util.hide('dialog'); + }) + $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); +},

    ====================================================== +Column and row menus

      onColumnHeaderClick: function(e) {
    +    this.state.currentColumn = $(e.target).closest('.column-header').attr('data-field');
    +    util.position('data-table-menu', e);
    +    util.render('columnActions', 'data-table-menu');
    +  },
    +
    +  onRowHeaderClick: function(e) {
    +    this.state.currentRow = $(e.target).parents('tr:first').attr('data-id');
    +    util.position('data-table-menu', e);
    +    util.render('rowActions', 'data-table-menu');
    +  },
    +  
    +  onRootHeaderClick: function(e) {
    +    util.position('data-table-menu', e);
    +    util.render('rootActions', 'data-table-menu', {'columns': this.hiddenFields});
    +  },
    +
    +  onMenuClick: function(e) {
    +    var self = this;
    +    e.preventDefault();
    +    var actions = {
    +      bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) },
    +      transform: function() { self.showTransformDialog('transform') },
    +      sortAsc: function() { self.setColumnSort('asc') },
    +      sortDesc: function() { self.setColumnSort('desc') },
    +      hideColumn: function() { self.hideColumn() },
    +      showColumn: function() { self.showColumn(e) },

    TODO: Delete or re-implement ...

          csv: function() { window.location.href = app.csvUrl },
    +      json: function() { window.location.href = "_rewrite/api/json" },
    +      urlImport: function() { showDialog('urlImport') },
    +      pasteImport: function() { showDialog('pasteImport') },
    +      uploadImport: function() { showDialog('uploadImport') },

    END TODO

          deleteColumn: function() {
    +        var msg = "Are you sure? This will delete '" + self.state.currentColumn + "' from all documents.";

    TODO:

            alert('This function needs to be re-implemented');
    +        return;
    +        if (confirm(msg)) costco.deleteColumn(self.state.currentColumn);
    +      },
    +      deleteRow: function() {
    +        var doc = _.find(self.model.currentDocuments.models, function(doc) {

    important this is == as the currentRow will be string (as comes +from DOM) while id may be int

              return doc.id == self.state.currentRow
    +        });
    +        doc.destroy().then(function() { 
    +            self.model.currentDocuments.remove(doc);
    +            my.notify("Row deleted successfully");
    +          })
    +          .fail(function(err) {
    +            my.notify("Errorz! " + err)
    +          })
    +      }
    +    }
    +    util.hide('data-table-menu');
    +    actions[$(e.target).attr('data-action')]();
    +  },
    +
    +  showTransformColumnDialog: function() {
    +    var $el = $('.dialog-content');
    +    util.show('dialog');
    +    var view = new my.ColumnTransform({
    +      model: this.model
    +    });
    +    view.state = this.state;
    +    view.render();
    +    $el.empty();
    +    $el.append(view.el);
    +    util.observeExit($el, function() {
    +      util.hide('dialog');
    +    })
    +    $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
    +  },
    +
    +  showTransformDialog: function() {
    +    var $el = $('.dialog-content');
    +    util.show('dialog');
    +    var view = new recline.View.DataTransform({
    +    });
    +    view.render();
    +    $el.empty();
    +    $el.append(view.el);
    +    util.observeExit($el, function() {
    +      util.hide('dialog');
    +    })
    +    $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
    +  },
    +
    +  setColumnSort: function(order) {
    +    var sort = [{}];
    +    sort[0][this.state.currentColumn] = {order: order};
    +    this.model.query({sort: sort});
    +  },
    +  
    +  hideColumn: function() {
    +    this.hiddenFields.push(this.state.currentColumn);
    +    this.render();
    +  },
    +  
    +  showColumn: function(e) {
    +    this.hiddenFields = _.without(this.hiddenFields, $(e.target).data('column'));
    +    this.render();
    +  },

    ======================================================

    + +

    Templating

      template: ' \
    +    <div class="data-table-menu-overlay" style="display: none; z-index: 101; ">&nbsp;</div> \
    +    <ul class="data-table-menu"></ul> \
    +    <table class="data-table" cellspacing="0"> \
    +      <thead> \
    +        <tr> \
    +          {{#notEmpty}} \
    +            <th class="column-header"> \
    +              <div class="column-header-title"> \
    +                <a class="root-header-menu"></a> \
    +                <span class="column-header-name"></span> \
    +              </div> \
    +            </th> \
    +          {{/notEmpty}} \
    +          {{#fields}} \
    +            <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}"> \
    +              <div class="column-header-title"> \
    +                <a class="column-header-menu"></a> \
    +                <span class="column-header-name">{{label}}</span> \
    +              </div> \
    +              </div> \
    +            </th> \
    +          {{/fields}} \
    +        </tr> \
    +      </thead> \
    +      <tbody></tbody> \
    +    </table> \
    +  ',
    +
    +  toTemplateJSON: function() {
    +    var modelData = this.model.toJSON()
    +    modelData.notEmpty = ( this.fields.length > 0 )

    TODO: move this sort of thing into a toTemplateJSON method on Dataset?

        modelData.fields = _.map(this.fields, function(field) { return field.toJSON() });
    +    return modelData;
    +  },
    +  render: function() {
    +    var self = this;
    +    this.fields = this.model.fields.filter(function(field) {
    +      return _.indexOf(self.hiddenFields, field.id) == -1;
    +    });
    +    var htmls = $.mustache(this.template, this.toTemplateJSON());
    +    this.el.html(htmls);
    +    this.model.currentDocuments.forEach(function(doc) {
    +      var tr = $('<tr />');
    +      self.el.find('tbody').append(tr);
    +      var newView = new my.DataGridRow({
    +          model: doc,
    +          el: tr,
    +          fields: self.fields,
    +        },
    +        self.options
    +        );
    +      newView.render();
    +    });
    +    this.el.toggleClass('no-hidden', (self.hiddenFields.length == 0));
    +    return this;
    +  }
    +});

    DataGridRow View for rendering an individual document.

    + +

    Since we want this to update in place it is up to creator to provider the element to attach to.

    + +

    In addition you must pass in a FieldList in the constructor options. This should be list of fields for the DataGrid.

    + +

    Additional options can be passed in a second hash argument. Options:

    + +
      +
    • cellRenderer: function to render cells. Signature: function(value, +field, doc) where value is the value of this cell, field is +corresponding field object and document is the document object. Note +that implementing functions can ignore arguments (e.g. +function(value) would be a valid cellRenderer function).
    • +
    + +

    Example:

    + +
    +var row = new DataGridRow({
    +  model: dataset-document,
    +    el: dom-element,
    +    fields: mydatasets.fields // a FieldList object
    +  }, {
    +    cellRenderer: my-cell-renderer-function 
    +  }
    +);
    +
    my.DataGridRow = Backbone.View.extend({
    +  initialize: function(initData, options) {
    +    _.bindAll(this, 'render');
    +    this._fields = initData.fields;
    +    if (options && options.cellRenderer) {
    +      this._cellRenderer = options.cellRenderer;
    +    } else {
    +      this._cellRenderer = function(value) {
    +        return value;
    +      }
    +    }
    +    this.el = $(this.el);
    +    this.model.bind('change', this.render);
    +  },
    +
    +  template: ' \
    +      <td><a class="row-header-menu"></a></td> \
    +      {{#cells}} \
    +      <td data-field="{{field}}"> \
    +        <div class="data-table-cell-content"> \
    +          <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
    +          <div class="data-table-cell-value">{{{value}}}</div> \
    +        </div> \
    +      </td> \
    +      {{/cells}} \
    +    ',
    +  events: {
    +    'click .data-table-cell-edit': 'onEditClick',
    +    'click .data-table-cell-editor .okButton': 'onEditorOK',
    +    'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
    +  },
    +  
    +  toTemplateJSON: function() {
    +    var self = this;
    +    var doc = this.model;
    +    var cellData = this._fields.map(function(field) {
    +      return {
    +        field: field.id,
    +        value: self._cellRenderer(doc.get(field.id), field, doc)
    +      }
    +    })
    +    return { id: this.id, cells: cellData }
    +  },
    +
    +  render: function() {
    +    this.el.attr('data-id', this.model.id);
    +    var html = $.mustache(this.template, this.toTemplateJSON());
    +    $(this.el).html(html);
    +    return this;
    +  },

    =================== +Cell Editor methods

      onEditClick: function(e) {
    +    var editing = this.el.find('.data-table-cell-editor-editor');
    +    if (editing.length > 0) {
    +      editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden");
    +    }
    +    $(e.target).addClass("hidden");
    +    var cell = $(e.target).siblings('.data-table-cell-value');
    +    cell.data("previousContents", cell.text());
    +    util.render('cellEditor', cell, {value: cell.text()});
    +  },
    +
    +  onEditorOK: function(e) {
    +    var cell = $(e.target);
    +    var rowId = cell.parents('tr').attr('data-id');
    +    var field = cell.parents('td').attr('data-field');
    +    var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
    +    var newData = {};
    +    newData[field] = newValue;
    +    this.model.set(newData);
    +    my.notify("Updating row...", {loader: true});
    +    this.model.save().then(function(response) {
    +        my.notify("Row updated successfully", {category: 'success'});
    +      })
    +      .fail(function() {
    +        my.notify('Error saving row', {
    +          category: 'error',
    +          persist: true
    +        });
    +      });
    +  },
    +
    +  onEditorCancel: function(e) {
    +    var cell = $(e.target).parents('.data-table-cell-value');
    +    cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
    +  }
    +});
    +
    +})(jQuery, recline.View);
    +
    +
    \ No newline at end of file diff --git a/docs/view.html b/docs/view.html index 18edf6c3..3237117f 100644 --- a/docs/view.html +++ b/docs/view.html @@ -1,4 +1,4 @@ - view.js

    view.js

    this.recline = this.recline || {};
    +      view.js           
    _.each(this.pageViews,function(view,pageName){$dataViewContainer.append(view.view.el)}); + varqueryEditor=newmy.QueryEditor({ + model:this.model.queryState + }); + this.el.find('.header').append(queryEditor.el);},setupRouting:function(){ @@ -177,273 +164,63 @@ note this.model and dataset returned are the same

    }});} -});

    view.js

    this.recline = this.recline || {};
     this.recline.View = this.recline.View || {};
     
     (function($, my) {

    DataExplorer

    @@ -22,14 +22,14 @@ var myExplorer = new model.recline.DataExplorer({

    views: (optional) the views (Grid, Graph etc) for DataExplorer to show. This is an array of view hashes. If not provided -just initialize a DataTable with id 'grid'. Example:

    +just initialize a DataGrid with id 'grid'. Example:

     var views = [
       {
         id: 'grid', // used for routing
         label: 'Grid', // used for view switcher
    -    view: new recline.View.DataTable({
    +    view: new recline.View.DataGrid({
           model: dataset
         })
       },
    @@ -46,7 +46,6 @@ var views = [
     

    config: Config options like:

      -
    • displayCount: how many documents to display initially (default: 10)
    • readOnly: true/false (default: false) value indicating whether to operate in read-only mode (hiding all editing options).
    @@ -63,10 +62,8 @@ FlotGraph subview.

    <li><a href="#{{id}}" class="btn">{{label}}</a> \ {{/views}} \ </ul> \ - <div class="pagination"> \ - <form class="display-count"> \ - Showing 0 to <input name="displayCount" type="text" value="{{displayCount}}" title="Edit and hit enter to change the number of rows displayed" /> of <span class="doc-count">{{docCount}}</span> \ - </form> \ + <div class="recline-results-info"> \ + Results found <span class="doc-count">{{docCount}}</span> \ </div> \ </div> \ <div class="data-view-container"></div> \ @@ -79,16 +76,11 @@ FlotGraph subview.

    </div> \ ', - events: { - 'submit form.display-count': 'onDisplayCountUpdate' - }, - initialize: function(options) { var self = this; this.el = $(this.el); this.config = _.extend({ - displayCount: 50 - , readOnly: false + readOnly: false }, options.config); if (this.config.readOnly) { @@ -99,46 +91,37 @@ FlotGraph subview.

    this.pageViews = [{ id: 'grid', label: 'Grid', - view: new my.DataTable({ + view: new my.DataGrid({ model: this.model }) }]; }

    this must be called after pageViews are created

        this.render();
     
         this.router = new Backbone.Router();
    -    this.setupRouting();

    retrieve basic data like fields etc + this.setupRouting(); + + this.model.bind('query:start', function(eventName) { + my.notify('Loading data', {loader: true}); + }); + this.model.bind('query:done', function(eventName) { + my.clearNotifications(); + self.el.find('.doc-count').text(self.model.docCount || 'Unknown'); + my.notify('Data loaded', {category: 'success'}); + }); + this.model.bind('query:fail', function(eventName, error) { + my.clearNotifications(); + my.notify(error.message, {category: 'error', persist: true}); + });

    retrieve basic data like fields etc note this.model and dataset returned are the same

        this.model.fetch()
           .done(function(dataset) {
             self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
    -        self.query();
    +        self.model.query();
           })
           .fail(function(error) {
             my.notify(error.message, {category: 'error', persist: true});
           });
       },
     
    -  query: function() {
    -    this.config.displayCount = parseInt(this.el.find('input[name="displayCount"]').val());
    -    var queryObj = {
    -      size: this.config.displayCount
    -    };
    -    my.notify('Loading data', {loader: true});
    -    this.model.query(queryObj)
    -      .done(function() {
    -        my.clearNotifications();
    -        my.notify('Data loaded', {category: 'success'});
    -      })
    -      .fail(function(error) {
    -        my.clearNotifications();
    -        my.notify(error.message, {category: 'error', persist: true});
    -      });
    -  },
    -
    -  onDisplayCountUpdate: function(e) {
    -    e.preventDefault();
    -    this.query();
    -  },
    -
       setReadOnly: function() {
         this.el.addClass('read-only');
       },
    @@ -153,6 +136,10 @@ note this.model and dataset returned are the same

    DataTable

    +}); -

    Provides a tabular view on a Dataset.

    -

    Initialize it with a recline.Dataset object.

    my.DataTable = Backbone.View.extend({
    -  tagName:  "div",
    -  className: "data-table-container",
    -
    -  initialize: function() {
    -    var self = this;
    -    this.el = $(this.el);
    -    _.bindAll(this, 'render');
    -    this.model.currentDocuments.bind('add', this.render);
    -    this.model.currentDocuments.bind('reset', this.render);
    -    this.model.currentDocuments.bind('remove', this.render);
    -    this.state = {};
    -    this.hiddenFields = [];
    -  },
    -
    -  events: {
    -    'click .column-header-menu': 'onColumnHeaderClick'
    -    , 'click .row-header-menu': 'onRowHeaderClick'
    -    , 'click .root-header-menu': 'onRootHeaderClick'
    -    , 'click .data-table-menu li a': 'onMenuClick'
    -  },

    TODO: delete or re-enable (currently this code is not used from anywhere except deprecated or disabled methods (see above)). -showDialog: function(template, data) { - if (!data) data = {}; - util.show('dialog'); - util.render(template, 'dialog-content', data); - util.observeExit($('.dialog-content'), function() { - util.hide('dialog'); - }) - $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' }); -},

    ====================================================== -Column and row menus

      onColumnHeaderClick: function(e) {
    -    this.state.currentColumn = $(e.target).closest('.column-header').attr('data-field');
    -    util.position('data-table-menu', e);
    -    util.render('columnActions', 'data-table-menu');
    -  },
    -
    -  onRowHeaderClick: function(e) {
    -    this.state.currentRow = $(e.target).parents('tr:first').attr('data-id');
    -    util.position('data-table-menu', e);
    -    util.render('rowActions', 'data-table-menu');
    -  },
    -  
    -  onRootHeaderClick: function(e) {
    -    util.position('data-table-menu', e);
    -    util.render('rootActions', 'data-table-menu', {'columns': this.hiddenFields});
    -  },
    -
    -  onMenuClick: function(e) {
    -    var self = this;
    -    e.preventDefault();
    -    var actions = {
    -      bulkEdit: function() { self.showTransformColumnDialog('bulkEdit', {name: self.state.currentColumn}) },
    -      transform: function() { self.showTransformDialog('transform') },
    -      sortAsc: function() { self.setColumnSort('asc') },
    -      sortDesc: function() { self.setColumnSort('desc') },
    -      hideColumn: function() { self.hideColumn() },
    -      showColumn: function() { self.showColumn(e) },

    TODO: Delete or re-implement ...

          csv: function() { window.location.href = app.csvUrl },
    -      json: function() { window.location.href = "_rewrite/api/json" },
    -      urlImport: function() { showDialog('urlImport') },
    -      pasteImport: function() { showDialog('pasteImport') },
    -      uploadImport: function() { showDialog('uploadImport') },

    END TODO

          deleteColumn: function() {
    -        var msg = "Are you sure? This will delete '" + self.state.currentColumn + "' from all documents.";

    TODO:

            alert('This function needs to be re-implemented');
    -        return;
    -        if (confirm(msg)) costco.deleteColumn(self.state.currentColumn);
    -      },
    -      deleteRow: function() {
    -        var doc = _.find(self.model.currentDocuments.models, function(doc) {

    important this is == as the currentRow will be string (as comes -from DOM) while id may be int

              return doc.id == self.state.currentRow
    -        });
    -        doc.destroy().then(function() { 
    -            self.model.currentDocuments.remove(doc);
    -            my.notify("Row deleted successfully");
    -          })
    -          .fail(function(err) {
    -            my.notify("Errorz! " + err)
    -          })
    -      }
    -    }
    -    util.hide('data-table-menu');
    -    actions[$(e.target).attr('data-action')]();
    -  },
    -
    -  showTransformColumnDialog: function() {
    -    var $el = $('.dialog-content');
    -    util.show('dialog');
    -    var view = new my.ColumnTransform({
    -      model: this.model
    -    });
    -    view.state = this.state;
    -    view.render();
    -    $el.empty();
    -    $el.append(view.el);
    -    util.observeExit($el, function() {
    -      util.hide('dialog');
    -    })
    -    $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
    -  },
    -
    -  showTransformDialog: function() {
    -    var $el = $('.dialog-content');
    -    util.show('dialog');
    -    var view = new recline.View.DataTransform({
    -    });
    -    view.render();
    -    $el.empty();
    -    $el.append(view.el);
    -    util.observeExit($el, function() {
    -      util.hide('dialog');
    -    })
    -    $('.dialog').draggable({ handle: '.dialog-header', cursor: 'move' });
    -  },
    -
    -  setColumnSort: function(order) {
    -    this.model.query({
    -      sort: [
    -        [this.state.currentColumn, order]
    -      ]
    -    });
    -  },
    -  
    -  hideColumn: function() {
    -    this.hiddenFields.push(this.state.currentColumn);
    -    this.render();
    -  },
    -  
    -  showColumn: function(e) {
    -    this.hiddenFields = _.without(this.hiddenFields, $(e.target).data('column'));
    -    this.render();
    -  },

    ======================================================

    - -

    Templating

      template: ' \
    -    <div class="data-table-menu-overlay" style="display: none; z-index: 101; ">&nbsp;</div> \
    -    <ul class="data-table-menu"></ul> \
    -    <table class="data-table" cellspacing="0"> \
    -      <thead> \
    -        <tr> \
    -          {{#notEmpty}} \
    -            <th class="column-header"> \
    -              <div class="column-header-title"> \
    -                <a class="root-header-menu"></a> \
    -                <span class="column-header-name"></span> \
    -              </div> \
    -            </th> \
    -          {{/notEmpty}} \
    -          {{#fields}} \
    -            <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}"> \
    -              <div class="column-header-title"> \
    -                <a class="column-header-menu"></a> \
    -                <span class="column-header-name">{{label}}</span> \
    -              </div> \
    -              </div> \
    -            </th> \
    -          {{/fields}} \
    -        </tr> \
    -      </thead> \
    -      <tbody></tbody> \
    -    </table> \
    +my.QueryEditor = Backbone.View.extend({
    +  className: 'recline-query-editor', 
    +  template: ' \
    +    <form action="" method="GET"> \
    +      <input type="text" name="q" value="{{q}}" class="text-query" /> \
    +      <div class="pagination"> \
    +        <ul> \
    +          <li class="prev action-pagination-update"><a>&laquo; back</a></li> \
    +          <li class="active"><a><input name="from" type="text" value="{{from}}" /> &ndash; <input name="to" type="text" value="{{to}}" /> </a></li> \
    +          <li class="next action-pagination-update"><a>next &raquo;</a></li> \
    +        </ul> \
    +      </div> \
    +      <button type="submit" class="btn" style="">Update &raquo;</button> \
    +    </form> \
       ',
     
    -  toTemplateJSON: function() {
    -    var modelData = this.model.toJSON()
    -    modelData.notEmpty = ( this.fields.length > 0 )

    TODO: move this sort of thing into a toTemplateJSON method on Dataset?

        modelData.fields = _.map(this.fields, function(field) { return field.toJSON() });
    -    return modelData;
    +  events: {
    +    'submit form': 'onFormSubmit',
    +    'click .action-pagination-update': 'onPaginationUpdate'
       },
    -  render: function() {
    -    var self = this;
    -    this.fields = this.model.fields.filter(function(field) {
    -      return _.indexOf(self.hiddenFields, field.id) == -1;
    -    });
    -    var htmls = $.mustache(this.template, this.toTemplateJSON());
    -    this.el.html(htmls);
    -    this.model.currentDocuments.forEach(function(doc) {
    -      var tr = $('<tr />');
    -      self.el.find('tbody').append(tr);
    -      var newView = new my.DataTableRow({
    -          model: doc,
    -          el: tr,
    -          fields: self.fields,
    -        });
    -      newView.render();
    -    });
    -    this.el.toggleClass('no-hidden', (self.hiddenFields.length == 0));
    -    return this;
    -  }
    -});

    DataTableRow View for rendering an individual document.

    -

    Since we want this to update in place it is up to creator to provider the element to attach to. -In addition you must pass in a fields in the constructor options. This should be list of fields for the DataTable.

    my.DataTableRow = Backbone.View.extend({
    -  initialize: function(options) {
    +  initialize: function() {
         _.bindAll(this, 'render');
    -    this._fields = options.fields;
         this.el = $(this.el);
         this.model.bind('change', this.render);
    +    this.render();
       },
    -
    -  template: ' \
    -      <td><a class="row-header-menu"></a></td> \
    -      {{#cells}} \
    -      <td data-field="{{field}}"> \
    -        <div class="data-table-cell-content"> \
    -          <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
    -          <div class="data-table-cell-value">{{value}}</div> \
    -        </div> \
    -      </td> \
    -      {{/cells}} \
    -    ',
    -  events: {
    -    'click .data-table-cell-edit': 'onEditClick',

    cell editor

        'click .data-table-cell-editor .okButton': 'onEditorOK',
    -    'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
    +  onFormSubmit: function(e) {
    +    e.preventDefault();
    +    var newFrom = parseInt(this.el.find('input[name="from"]').val());
    +    var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom;
    +    var query = this.el.find('.text-query').val();
    +    this.model.set({size: newSize, from: newFrom, q: query});
       },
    -  
    -  toTemplateJSON: function() {
    -    var doc = this.model;
    -    var cellData = this._fields.map(function(field) {
    -      return {field: field.id, value: doc.get(field.id)}
    -    })
    -    return { id: this.id, cells: cellData }
    -  },
    -
    -  render: function() {
    -    this.el.attr('data-id', this.model.id);
    -    var html = $.mustache(this.template, this.toTemplateJSON());
    -    $(this.el).html(html);
    -    return this;
    -  },

    Cell Editor

      onEditClick: function(e) {
    -    var editing = this.el.find('.data-table-cell-editor-editor');
    -    if (editing.length > 0) {
    -      editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden");
    +  onPaginationUpdate: function(e) {
    +    e.preventDefault();
    +    var $el = $(e.target);
    +    if ($el.parent().hasClass('prev')) {
    +      var newFrom = this.model.get('from') - Math.max(0, this.model.get('size'));
    +    } else {
    +      var newFrom = this.model.get('from') + this.model.get('size');
         }
    -    $(e.target).addClass("hidden");
    -    var cell = $(e.target).siblings('.data-table-cell-value');
    -    cell.data("previousContents", cell.text());
    -    util.render('cellEditor', cell, {value: cell.text()});
    +    this.model.set({from: newFrom});
       },
    -
    -  onEditorOK: function(e) {
    -    var cell = $(e.target);
    -    var rowId = cell.parents('tr').attr('data-id');
    -    var field = cell.parents('td').attr('data-field');
    -    var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
    -    var newData = {};
    -    newData[field] = newValue;
    -    this.model.set(newData);
    -    my.notify("Updating row...", {loader: true});
    -    this.model.save().then(function(response) {
    -        my.notify("Row updated successfully", {category: 'success'});
    -      })
    -      .fail(function() {
    -        my.notify('Error saving row', {
    -          category: 'error',
    -          persist: true
    -        });
    -      });
    -  },
    -
    -  onEditorCancel: function(e) {
    -    var cell = $(e.target).parents('.data-table-cell-value');
    -    cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
    +  render: function() {
    +    var tmplData = this.model.toJSON();
    +    tmplData.to = this.model.get('from') + this.model.get('size');
    +    var templated = $.mustache(this.template, tmplData);
    +    this.el.html(templated);
       }
     });
     
     
    -/* ========================================================== */

    Miscellaneous Utilities

    var urlPathRegex = /^([^?]+)(\?.*)?/;

    Parse the Hash section of a URL into path and query string

    my.parseHashUrl = function(hashUrl) {
    +/* ========================================================== */

    Miscellaneous Utilities

    var urlPathRegex = /^([^?]+)(\?.*)?/;

    Parse the Hash section of a URL into path and query string

    my.parseHashUrl = function(hashUrl) {
       var parsed = urlPathRegex.exec(hashUrl);
       if (parsed == null) {
         return {};
    @@ -453,7 +230,7 @@ In addition you must pass in a fields in the constructor options. This should be
           query: parsed[2] || ''
         }
       }
    -}

    Parse a URL query string (?xyz=abc...) into a dictionary.

    my.parseQueryString = function(q) {
    +}

    Parse a URL query string (?xyz=abc...) into a dictionary.

    my.parseQueryString = function(q) {
       var urlParams = {},
         e, d = function (s) {
           return unescape(s.replace(/\+/g, " "));
    @@ -463,13 +240,13 @@ In addition you must pass in a fields in the constructor options. This should be
       if (q && q.length && q[0] === '?') {
         q = q.slice(1);
       }
    -  while (e = r.exec(q)) {

    TODO: have values be array as query string allow repetition of keys

        urlParams[d(e[1])] = d(e[2]);
    +  while (e = r.exec(q)) {

    TODO: have values be array as query string allow repetition of keys

        urlParams[d(e[1])] = d(e[2]);
       }
       return urlParams;
    -}

    Parse the query string out of the URL hash

    my.parseHashQueryString = function() {
    +}

    Parse the query string out of the URL hash

    my.parseHashQueryString = function() {
       q = my.parseHashUrl(window.location.hash).query;
       return my.parseQueryString(q);
    -}

    Compse a Query String

    my.composeQueryString = function(queryParams) {
    +}

    Compse a Query String

    my.composeQueryString = function(queryParams) {
       var queryString = '?';
       var items = [];
       $.each(queryParams, function(key, value) {
    @@ -481,7 +258,7 @@ In addition you must pass in a fields in the constructor options. This should be
     
     my.setHashQueryString = function(queryParams) {
       window.location.hash = window.location.hash.split('?')[0] + my.composeQueryString(queryParams);
    -}

    notify

    +}

    notify

    Create a notification (a div.alert-message in div.alert-messsages) using provide messages and options. Options are:

    @@ -513,7 +290,7 @@ In addition you must pass in a fields in the constructor options. This should be }); }, 1000); } -}

    clearNotifications

    +}

    clearNotifications

    Clear all existing notifications

    my.clearNotifications = function() {
       var $notifications = $('.data-explorer .alert-message');
    diff --git a/index.html b/index.html
    index 1a580291..0c18e8ed 100644
    --- a/index.html
    +++ b/index.html
    @@ -196,9 +196,12 @@ like).

    Source Docs (via Docco)

    Tests

    diff --git a/src/backend/base.js b/src/backend/base.js index 60b44225..caf317f9 100644 --- a/src/backend/base.js +++ b/src/backend/base.js @@ -2,9 +2,7 @@ // // Backends are connectors to backend data sources and stores // -// Backends are implemented as Backbone models but this is just a -// convenience (they do not save or load themselves from any remote -// source) +// This is just the base module containing various convenience methods. this.recline = this.recline || {}; this.recline.Backend = this.recline.Backend || {}; diff --git a/src/backend/elasticsearch.js b/src/backend/elasticsearch.js index 7bcd338e..78e27af6 100644 --- a/src/backend/elasticsearch.js +++ b/src/backend/elasticsearch.js @@ -4,10 +4,21 @@ this.recline.Backend = this.recline.Backend || {}; (function($, my) { // ## ElasticSearch Backend // - // Connecting to [ElasticSearch](http://www.elasticsearch.org/) + // Connecting to [ElasticSearch](http://www.elasticsearch.org/). // - // To use this backend ensure your Dataset has a elasticsearch_url, - // webstore_url or url attribute (used in that order) + // To use this backend ensure your Dataset has one of the following + // attributes (first one found is used): + // + //
    +  // elasticsearch_url
    +  // webstore_url
    +  // url
    +  // 
    + // + // This should point to the ES type url. E.G. for ES running on + // localhost:9200 with index twitter and type tweet it would be + // + //
    http://localhost:9200/twitter/tweet
    my.ElasticSearch = Backbone.Model.extend({ _getESUrl: function(dataset) { var out = dataset.get('elasticsearch_url'); diff --git a/src/backend/memory.js b/src/backend/memory.js index d5a43acd..6da45b6b 100644 --- a/src/backend/memory.js +++ b/src/backend/memory.js @@ -4,15 +4,12 @@ this.recline.Backend = this.recline.Backend || {}; (function($, my) { // ## Memory Backend - uses in-memory data // - // This is very artificial and is really only designed for testing - // purposes. - // // To use it you should provide in your constructor data: // // * metadata (including fields array) // * documents: list of hashes, each hash being one doc. A doc *must* have an id attribute which is unique. // - // Example: + // Example: // //
       //  // Backend setup
    @@ -29,7 +26,7 @@ this.recline.Backend = this.recline.Backend || {};
       //      ]
       //  });
       //  // later ...
    -  //  var dataset = Dataset({id: 'my-id'});
    +  //  var dataset = Dataset({id: 'my-id'}, 'memory');
       //  dataset.fetch();
       //  etc ...
       //  
    diff --git a/src/view-grid.js b/src/view-grid.js index 3ab4b6fe..c079226b 100644 --- a/src/view-grid.js +++ b/src/view-grid.js @@ -220,7 +220,8 @@ my.DataGrid = Backbone.View.extend({ // ## DataGridRow View for rendering an individual document. // // Since we want this to update in place it is up to creator to provider the element to attach to. -// In addition you must pass in a fields in the constructor options. This should be list of fields for the DataGrid. +// +// In addition you *must* pass in a FieldList in the constructor options. This should be list of fields for the DataGrid. // // Additional options can be passed in a second hash argument. Options: // @@ -229,6 +230,19 @@ my.DataGrid = Backbone.View.extend({ // corresponding field object and document is the document object. Note // that implementing functions can ignore arguments (e.g. // function(value) would be a valid cellRenderer function). +// +// Example: +// +//
    +// var row = new DataGridRow({
    +//   model: dataset-document,
    +//     el: dom-element,
    +//     fields: mydatasets.fields // a FieldList object
    +//   }, {
    +//     cellRenderer: my-cell-renderer-function 
    +//   }
    +// );
    +// 
    my.DataGridRow = Backbone.View.extend({ initialize: function(initData, options) { _.bindAll(this, 'render'); @@ -280,9 +294,8 @@ my.DataGridRow = Backbone.View.extend({ return this; }, - // Cell Editor - // =========== - + // =================== + // Cell Editor methods onEditClick: function(e) { var editing = this.el.find('.data-table-cell-editor-editor'); if (editing.length > 0) {