From e480777cb28226c705fe4a1d514b76b068ca6b2f Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 17 Feb 2012 09:39:38 +0000 Subject: [PATCH] [docs][s]: build latest docco docs for backend and model. --- docs/backend.html | 315 +++++++++++++++++++++++++--------------------- docs/model.html | 35 ++++-- 2 files changed, 199 insertions(+), 151 deletions(-) diff --git a/docs/backend.html b/docs/backend.html index 26bd5b61..e49f5bbe 100644 --- a/docs/backend.html +++ b/docs/backend.html @@ -7,106 +7,132 @@ convenience (they do not save or load themselves from any remote source)

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

Backbone.sync

- Backbone.sync = function(method, model, options) { - return my.backends[model.backendConfig.type].sync(method, model, options); - }

BackendMemory - uses in-memory data

+

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

-

To use you should:

+

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

-

A. provide metadata as model data to the Dataset

+

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

-

B. Set backendConfig on your dataset with attributes:

+

To use it you should provide in your constructor data:

  my.BackendMemory = Backbone.Model.extend({
-      sync: function(method, model, options) {
-        var self = this;
-        if (method === "read") {
-          var dfd = $.Deferred();
-          if (model.__type__ == 'Dataset') {
-            var dataset = model;
-            dataset.set({
-              headers: dataset.backendConfig.data.headers
-            });
-            dataset.docCount = dataset.backendConfig.data.rows.length;
-            dfd.resolve(dataset);
-          }
-          return dfd.promise();
-        } else if (method === 'update') {
-          var dfd = $.Deferred();
-          if (model.__type__ == 'Document') {
-            _.each(model.backendConfig.data.rows, function(row, idx) {
-              if(row.id === model.id) {
-                model.backendConfig.data.rows[idx] = model.toJSON();
-              }
-            });
-            dfd.resolve(model);
-          }
-          return dfd.promise();
-        } else if (method === 'delete') {
-          var dfd = $.Deferred();
-          if (model.__type__ == 'Document') {
-            model.backendConfig.data.rows = _.reject(model.backendConfig.data.rows, function(row) {
-              return (row.id === model.id);
-            });
-            dfd.resolve(model);
-          }
-          return dfd.promise();
-        } else {
-          alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model);
-        }
-      },
-      getDocuments: function(model, numRows, start) {
-        if (start === undefined) {
-          start = 0;
-        }
-        if (numRows === undefined) {
-          numRows = 10;
-        }
+    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();
-        rows = model.backendConfig.data.rows;
-        var results = rows.slice(start, start+numRows);
-        dfd.resolve(results);
+        if (model.__type__ == 'Dataset') {
+          var rawDataset = this.datasets[model.id];
+          model.set(rawDataset.metadata);
+          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

+ my.backends['memory'] = new my.BackendMemory();

BackendWebstore

Connecting to Webstores

-

To use this backend set backendConfig on your Dataset as:

- -
-{
-  'type': 'webstore',
-  'url': url to relevant Webstore table
-}
-
  my.BackendWebstore = Backbone.Model.extend({
+

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 dataset = model;
-          var base = dataset.backendConfig.url;
+          var base = model.get('webstore_url');
           var schemaUrl = base + '/schema.json';
           var jqxhr = $.ajax({
             url: schemaUrl,
@@ -114,72 +140,72 @@ source)

jsonp: '_callback' }); var dfd = $.Deferred(); - jqxhr.then(function(schema) { + wrapInTimeout(jqxhr).done(function(schema) { headers = _.map(schema.data, function(item) { return item.name; }); - dataset.set({ + model.set({ headers: headers }); - dataset.docCount = schema.count; - dfd.resolve(dataset, jqxhr); + model.docCount = schema.count; + dfd.resolve(model, jqxhr); + }) + .fail(function(arguments) { + dfd.reject(arguments); }); return dfd.promise(); } } }, - getDocuments: function(model, numRows, start) { - if (start === undefined) { - start = 0; - } - if (numRows === undefined) { - numRows = 10; - } - var base = model.backendConfig.url; + query: function(model, queryObj) { + var base = model.get('webstore_url'); + var data = { + _limit: queryObj.size + , _offset: queryObj.offset + }; var jqxhr = $.ajax({ - url: base + '.json?_limit=' + numRows, - dataType: 'jsonp', - jsonp: '_callback', - cache: true + url: base + '.json', + data: data, + dataType: 'jsonp', + jsonp: '_callback', + cache: true }); var dfd = $.Deferred(); - jqxhr.then(function(results) { + jqxhr.done(function(results) { dfd.resolve(results.data); }); return dfd.promise(); } }); - my.backends['webstore'] = new my.BackendWebstore();

BackendDataProxy

+ my.backends['webstore'] = new my.BackendWebstore();

BackendDataProxy

For connecting to DataProxy-s.

-

Set a Dataset to use this backend:

- -
dataset.backendConfig = {
-  // required
-  url: {url-of-data-to-proxy},
-  format: csv | xls,
-}
-
-

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

+

Datasets using using this backend should set the following attributes:

+ + +

Note that this is a read-only backend.

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

TODO: should we cache for extra efficiency

          var data = {
-            url: dataset.backendConfig.url
+          var base = self.get('dataproxy_url');

TODO: should we cache for extra efficiency

          var data = {
+            url: model.get('url')
             , 'max-results':  1
-            , type: dataset.backendConfig.format
+            , type: model.get('format') || 'csv'
           };
           var jqxhr = $.ajax({
             url: base
@@ -187,11 +213,14 @@ source)

, dataType: 'jsonp' }); var dfd = $.Deferred(); - jqxhr.then(function(results) { - dataset.set({ + wrapInTimeout(jqxhr).done(function(results) { + model.set({ headers: results.fields }); - dfd.resolve(dataset, jqxhr); + dfd.resolve(model, jqxhr); + }) + .fail(function(arguments) { + dfd.reject(arguments); }); return dfd.promise(); } @@ -199,18 +228,12 @@ source)

alert('This backend only supports read operations'); } }, - getDocuments: function(dataset, numRows, start) { - if (start === undefined) { - start = 0; - } - if (numRows === undefined) { - numRows = 10; - } - var base = my.backends['dataproxy'].get('dataproxy'); + query: function(dataset, queryObj) { + var base = this.get('dataproxy_url'); var data = { - url: dataset.backendConfig.url - , 'max-results': numRows - , type: dataset.backendConfig.format + url: dataset.get('url') + , 'max-results': queryObj.size + , type: dataset.get('format') }; var jqxhr = $.ajax({ url: base @@ -218,11 +241,11 @@ source)

, dataType: 'jsonp' }); var dfd = $.Deferred(); - jqxhr.then(function(results) { - var _out = _.map(results.data, function(row) { + jqxhr.done(function(results) { + var _out = _.map(results.data, function(doc) { var tmp = {}; _.each(results.fields, function(key, idx) { - tmp[key] = row[idx]; + tmp[key] = doc[idx]; }); return tmp; }); @@ -231,25 +254,37 @@ source)

return dfd.promise(); } }); - my.backends['dataproxy'] = new my.BackendDataProxy();

Google spreadsheet backend

+ my.backends['dataproxy'] = new my.BackendDataProxy();

Google spreadsheet backend

-

Connect to Google Docs spreadsheet. For write operations

  my.BackendGDoc = Backbone.Model.extend({
+

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.backendConfig.url, function(d) {
-          result = my.backends['gdocs'].gdocsToJavascript(d);
-          model.set({'headers': result.header});

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

          model._dataCache = result.data;
+        $.getJSON(model.get('url'), function(d) {
+          result = self.gdocsToJavascript(d);
+          model.set({'headers': result.header});

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

          model._dataCache = result.data;
           dfd.resolve(model);
         })
         return dfd.promise(); }
     },
 
-    getDocuments: function(dataset, start, numRows) { 
+    query: function(dataset, queryObj) { 
       var dfd = $.Deferred();
-      var fields = dataset.get('headers');

zip the field headers with the data rows to produce js objs + var fields = dataset.get('headers');

zip the field headers 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]; })
@@ -274,11 +309,11 @@ TODO: factor this out as a common method with other backends

var results = { 'header': [], 'data': [] - };

default is no special info on type of columns

      var colTypes = {};
+      };

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.header = options.columnsToUse;
-      } else {

set columns to use to be all available

        if (gdocsSpreadsheet.feed.entry.length > 0) {
+      }

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

      if (options.columnsToUse) {

columns set to subset supplied

        results.header = 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)
@@ -286,13 +321,13 @@ TODO: factor this out as a common method with other backends

} } } - }

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

      var rep = /^([\d\.\-]+)\%$/;
+      }

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.header) {
           var col = results.header[k];
           var _keyname = 'gsx$' + col;
-          var value = entry[_keyname]['$t'];

if labelled as % and value contains %, convert

          if (colTypes[col] == 'percent') {
+          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);
diff --git a/docs/model.html b/docs/model.html
index 74e34674..f6fee7df 100644
--- a/docs/model.html
+++ b/docs/model.html
@@ -10,11 +10,17 @@
 
  • docCount: total number of documents in this dataset (obtained on a fetch for this Dataset)
  •   my.Dataset = Backbone.Model.extend({
         __type__: 'Dataset',
    -    initialize: function(options) {
    +    initialize: function(model, backend) {
    +      this.backend = backend;
    +      if (backend && backend.constructor == String) {
    +        this.backend = my.backends[backend];
    +      }
           this.currentDocuments = new my.DocumentList();
           this.docCount = null;
    -      this.backend = null;
    -    },

    getDocuments

    + this.defaultQuery = { + size: 100 + , offset: 0 + };

    this.queryState = {};

        },

    getDocuments

    AJAX method with promise API to get rows (documents) from the backend.

    @@ -25,19 +31,23 @@ also returned.

    :param start: passed onto backend getDocuments.

    this does not fit very well with Backbone setup. Backbone really expects you to know the ids of objects your are fetching (which you do in classic RESTful ajax-y world). But this paradigm does not fill well with data set up we have here. -This also illustrates the limitations of separating the Dataset and the Backend

        getDocuments: function(numRows, start) {
    +This also illustrates the limitations of separating the Dataset and the Backend

        query: function(queryObj) {
           var self = this;
    -      var backend = my.backends[this.backendConfig.type];
    +      this.queryState = queryObj || this.defaultQuery;
    +      this.queryState = _.extend({size: 100, offset: 0}, this.queryState);
           var dfd = $.Deferred();
    -      backend.getDocuments(this, numRows, start).then(function(rows) {
    +      this.backend.query(this, this.queryState).done(function(rows) {
             var docs = _.map(rows, function(row) {
               var _doc = new my.Document(row);
    -          _doc.backendConfig = self.backendConfig;
    -          _doc.backend = backend;
    +          _doc.backend = self.backend;
    +          _doc.dataset = self;
               return _doc;
             });
             self.currentDocuments.reset(docs);
             dfd.resolve(self.currentDocuments);
    +      })
    +      .fail(function(arguments) {
    +        dfd.reject(arguments);
           });
           return dfd.promise();
         },
    @@ -47,14 +57,17 @@ This also illustrates the limitations of separating the Dataset and the Backend<
           data.docCount = this.docCount;
           return data;
         }
    -  });

    A Document (aka Row)

    + });

    A Document (aka Row)

    A single entry or row in the dataset

      my.Document = Backbone.Model.extend({
         __type__: 'Document'
    -  });

    A Backbone collection of Documents

      my.DocumentList = Backbone.Collection.extend({
    +  });

    A Backbone collection of Documents

      my.DocumentList = Backbone.Collection.extend({
         __type__: 'DocumentList',
         model: my.Document
    -  });
    +  });

    Backend registry

    + +

    Backends will register themselves by id into this registry

      my.backends = {};
    +
     }(jQuery, this.recline.Model));
     
     
    \ No newline at end of file