From c1c18816600325685ba9494ee6eaeff2d53eb14e Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 31 May 2012 21:04:31 +0100 Subject: [PATCH 1/8] [#129,refactor][l]: ([s] in effort) rename Document to Record - fixes #129. --- library.html | 18 +++++------ src/backend/base.js | 18 +++++------ src/backend/elasticsearch.js | 14 ++++----- src/backend/memory.js | 14 ++++----- src/model.js | 42 +++++++++++++------------- src/view-graph.js | 18 +++++------ src/view-grid.js | 16 +++++----- src/view-map.js | 30 +++++++++---------- src/view-slickgrid.js | 8 ++--- src/view-timeline.js | 8 ++--- src/view-transform-dialog.js | 8 ++--- test/backend.elasticsearch.test.js | 48 +++++++++++++++--------------- test/backend/csv.test.js | 2 +- test/backend/memory.test.js | 40 ++++++++++++------------- test/model.test.js | 4 +-- test/view-grid.test.js | 2 +- test/view-map.test.js | 16 +++++----- test/view-slickgrid.test.js | 2 +- test/view-timeline.test.js | 12 ++++---- 19 files changed, 160 insertions(+), 160 deletions(-) diff --git a/library.html b/library.html index 7d654861..3593bbc9 100644 --- a/library.html +++ b/library.html @@ -13,7 +13,7 @@ title: Library - Home

Building on Backbone, Recline supplies components and structure to data-heavy applications by providing a - set of models (Dataset, Document/Row, Field) and views (Grid, Map, Graph + set of models (Dataset, Record/Row, Field) and views (Grid, Map, Graph etc).

Examples

@@ -52,7 +52,7 @@ title: Library - Home

Create a new Backend

-

Create a Custom Document Object

+

Create a Custom Record Object

@@ -70,10 +70,10 @@ title: Library - Home

There are two main model objects:

@@ -84,7 +84,7 @@ title: Library - Home
  • Query: an object to encapsulate a query to the backend (useful both for creating queries and for storing and manipulating query state - e.g. from a query editor).
  • -
  • Facet: Object to store Facet +
  • Facet: Object to store Facet information, that is summary information (e.g. values and counts) about a field obtained by some faceting method on the backend.
  • @@ -97,13 +97,13 @@ title: Library - Home

    Backends

    -

    Backends connect Dataset and Documents to data from a +

    Backends connect Dataset and Records to data from a specific 'Backend' data source. They provide methods for loading and saving - Datasets and individuals Documents as well as for bulk loading via a query API + Datasets and individuals Records as well as for bulk loading via a query API and doing bulk transforms on the backend.

    A template Base class can be found in the - Backend base module of the source docs. It documents both the relevant + Backend base module of the source docs. It records both the relevant methods a Backend must have and (optionally) provides a base 'class' for inheritance. You can also find detailed examples of backend implementations in the source documentation below.

    diff --git a/src/backend/base.js b/src/backend/base.js index 2758f51e..1948de6e 100644 --- a/src/backend/base.js +++ b/src/backend/base.js @@ -29,14 +29,14 @@ this.recline.Backend.Base = function() { // ### sync // // An implementation of Backbone.sync that will be used to override - // Backbone.sync on operations for Datasets and Documents which are using this backend. + // Backbone.sync on operations for Datasets and Records which are using this backend. // // For read-only implementations you will need only to implement read method // for Dataset models (and even this can be a null operation). The read method // should return relevant metadata for the Dataset. We do not require read support - // for Documents because they are loaded in bulk by the query method. + // for Records because they are loaded in bulk by the query method. // - // For backends supporting write operations you must implement update and delete support for Document objects. + // For backends supporting write operations you must implement update and delete support for Record objects. // // All code paths should return an object conforming to the jquery promise API. this.sync = function(method, model, options) { @@ -44,8 +44,8 @@ this.recline.Backend.Base = function() { // ### query // - // Query the backend for documents returning them in bulk. This method will - // be used by the Dataset.query method to search the backend for documents, + // Query the backend for records returning them in bulk. This method will + // be used by the Dataset.query method to search the backend for records, // retrieving the results in bulk. // // @param {recline.model.Dataset} model: Dataset model. @@ -71,11 +71,11 @@ this.recline.Backend.Base = function() { //
       // {
       //   total: // (required) total number of results (can be null)
    -  //   hits: [ // (required) one entry for each result document
    +  //   hits: [ // (required) one entry for each result record
       //     {
    -  //        _score:   // (optional) match score for document
    -  //        _type: // (optional) document type
    -  //        _source: // (required) document/row object
    +  //        _score:   // (optional) match score for record
    +  //        _type: // (optional) record type
    +  //        _source: // (required) record/row object
       //     } 
       //   ],
       //   facets: { // (optional) 
    diff --git a/src/backend/elasticsearch.js b/src/backend/elasticsearch.js
    index a73d4140..bcab5d95 100644
    --- a/src/backend/elasticsearch.js
    +++ b/src/backend/elasticsearch.js
    @@ -39,7 +39,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
     
         // ### get
         //
    -    // Get document corresponding to specified id
    +    // Get record corresponding to specified id
         //
         // @return promise compatible deferred object.
         this.get = function(id) {
    @@ -52,7 +52,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
     
         // ### upsert
         //
    -    // create / update a document to ElasticSearch backend
    +    // create / update a record to ElasticSearch backend
         //
         // @param {Object} doc an object to insert to the index.
         // @return deferred supporting promise API
    @@ -72,7 +72,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
     
         // ### delete
         //
    -    // Delete a document from the ElasticSearch backend.
    +    // Delete a record from the ElasticSearch backend.
         //
         // @param {Object} id id of object to delete
         // @return deferred supporting promise API
    @@ -154,7 +154,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
         // Backbone sync implementation for this backend.
         //
         // URL of ElasticSearch endpoint to use must be specified on the dataset
    -    // (and on a Document via its dataset attribute) by the dataset having a
    +    // (and on a Record via its dataset attribute) by the dataset having a
         // url attribute.
         this.sync = function(method, model, options) {
           if (model.__type__ == 'Dataset') {
    @@ -180,15 +180,15 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
                 dfd.reject(arguments);
               });
               return dfd.promise();
    -        } else if (model.__type__ == 'Document') {
    +        } else if (model.__type__ == 'Record') {
               return es.get(model.dataset.id);
             }
           } else if (method === 'update') {
    -        if (model.__type__ == 'Document') {
    +        if (model.__type__ == 'Record') {
               return es.upsert(model.toJSON());
             }
           } else if (method === 'delete') {
    -        if (model.__type__ == 'Document') {
    +        if (model.__type__ == 'Record') {
               return es.delete(model.id);
             }
           }
    diff --git a/src/backend/memory.js b/src/backend/memory.js
    index f7fa8afb..60ef7811 100644
    --- a/src/backend/memory.js
    +++ b/src/backend/memory.js
    @@ -7,7 +7,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
       //
       // Convenience function to create a simple 'in-memory' dataset in one step.
       //
    -  // @param data: list of hashes for each document/row in the data ({key:
    +  // @param data: list of hashes for each record/row in the data ({key:
       // value, key: value})
       // @param fields: (optional) list of field hashes (each hash defining a hash
       // as per recline.Model.Field). If fields not specified they will be taken
    @@ -76,7 +76,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
           results = results.slice(start, start+numRows);
           return {
             total: total,
    -        documents: results,
    +        records: results,
             facets: facets
           };
         };
    @@ -118,7 +118,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
           return results;
         };
     
    -    this.computeFacets = function(documents, queryObj) {
    +    this.computeFacets = function(records, queryObj) {
           var facetResults = {};
           if (!queryObj.facets) {
             return facetResults;
    @@ -129,7 +129,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
             facetResults[facetId].termsall = {};
           });
           // faceting
    -      _.each(documents, function(doc) {
    +      _.each(records, function(doc) {
             _.each(queryObj.facets, function(query, facetId) {
               var fieldId = query.terms.field;
               var val = doc[fieldId];
    @@ -172,13 +172,13 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
             }
             return dfd.promise();
           } else if (method === 'update') {
    -        if (model.__type__ == 'Document') {
    +        if (model.__type__ == 'Record') {
               model.dataset._dataCache.update(model.toJSON());
               dfd.resolve(model);
             }
             return dfd.promise();
           } else if (method === 'delete') {
    -        if (model.__type__ == 'Document') {
    +        if (model.__type__ == 'Record') {
               model.dataset._dataCache.delete(model.toJSON());
               dfd.resolve(model);
             }
    @@ -191,7 +191,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
         this.query = function(model, queryObj) {
           var dfd = $.Deferred();
           var results = model._dataCache.query(queryObj);
    -      var hits = _.map(results.documents, function(row) {
    +      var hits = _.map(results.records, function(row) {
             return { _source: row };
           });
           var out = {
    diff --git a/src/model.js b/src/model.js
    index a32e5521..99ef250f 100644
    --- a/src/model.js
    +++ b/src/model.js
    @@ -12,11 +12,11 @@ this.recline.Model = this.recline.Model || {};
     // fields on this Dataset (this can be set explicitly, or, will be set by
     // Dataset.fetch() or Dataset.query()
     //
    -// @property {DocumentList} currentDocuments: a `DocumentList` containing the
    -// Documents we have currently loaded for viewing (updated by calling query
    +// @property {RecordList} currentRecords: a `RecordList` containing the
    +// Records we have currently loaded for viewing (updated by calling query
     // method)
     //
    -// @property {number} docCount: total number of documents in this dataset
    +// @property {number} docCount: total number of records in this dataset
     //
     // @property {Backend} backend: the Backend (instance) for this Dataset.
     //
    @@ -48,7 +48,7 @@ my.Dataset = Backbone.Model.extend({
           this.backend = this._backendFromString(backend);
         }
         this.fields = new my.FieldList();
    -    this.currentDocuments = new my.DocumentList();
    +    this.currentRecords = new my.RecordList();
         this.facets = new my.FacetList();
         this.docCount = null;
         this.queryState = new my.Query();
    @@ -58,12 +58,12 @@ my.Dataset = Backbone.Model.extend({
     
       // ### query
       //
    -  // AJAX method with promise API to get documents from the backend.
    +  // AJAX method with promise API to get records from the backend.
       //
       // It will query based on current query state (given by this.queryState)
       // updated by queryObj (if provided).
       //
    -  // Resulting DocumentList are used to reset this.currentDocuments and are
    +  // Resulting RecordList are used to reset this.currentRecords and are
       // also returned.
       query: function(queryObj) {
         var self = this;
    @@ -73,12 +73,12 @@ my.Dataset = Backbone.Model.extend({
         this.backend.query(this, actualQuery).done(function(queryResult) {
           self.docCount = queryResult.total;
           var docs = _.map(queryResult.hits, function(hit) {
    -        var _doc = new my.Document(hit._source);
    +        var _doc = new my.Record(hit._source);
             _doc.backend = self.backend;
             _doc.dataset = self;
             return _doc;
           });
    -      self.currentDocuments.reset(docs);
    +      self.currentRecords.reset(docs);
           if (queryResult.facets) {
             var facets = _.map(queryResult.facets, function(facetResult, facetId) {
               facetResult.id = facetId;
    @@ -87,7 +87,7 @@ my.Dataset = Backbone.Model.extend({
             self.facets.reset(facets);
           }
           self.trigger('query:done');
    -      dfd.resolve(self.currentDocuments);
    +      dfd.resolve(self.currentRecords);
         })
         .fail(function(arguments) {
           self.trigger('query:fail', arguments);
    @@ -176,11 +176,11 @@ my.Dataset.restore = function(state) {
       return dataset;
     };
     
    -// ## A Document (aka Row)
    +// ## A Record (aka Row)
     // 
     // A single entry or row in the dataset
    -my.Document = Backbone.Model.extend({
    -  __type__: 'Document',
    +my.Record = Backbone.Model.extend({
    +  __type__: 'Record',
       initialize: function() {
         _.bindAll(this, 'getFieldValue');
       },
    @@ -188,7 +188,7 @@ my.Document = Backbone.Model.extend({
       // ### getFieldValue
       //
       // For the provided Field get the corresponding rendered computed data value
    -  // for this document.
    +  // for this record.
       getFieldValue: function(field) {
         var val = this.get(field.id);
         if (field.deriver) {
    @@ -211,17 +211,17 @@ my.Document = Backbone.Model.extend({
       }
     });
     
    -// ## A Backbone collection of Documents
    -my.DocumentList = Backbone.Collection.extend({
    -  __type__: 'DocumentList',
    -  model: my.Document
    +// ## A Backbone collection of Records
    +my.RecordList = Backbone.Collection.extend({
    +  __type__: 'RecordList',
    +  model: my.Record
     });
     
     // ## A Field (aka Column) on a Dataset
     // 
     // Following (Backbone) attributes as standard:
     //
    -// * id: a unique identifer for this field- usually this should match the key in the documents hash
    +// * id: a unique identifer for this field- usually this should match the key in the records hash
     // * label: (optional: defaults to id) the visible label used for this field
     // * type: (optional: defaults to string) the type of the data in this field. Should be a string as per type names defined by ElasticSearch - see Types list on 
     // * format: (optional) used to indicate how the data should be formatted. For example:
    @@ -234,13 +234,13 @@ my.DocumentList = Backbone.Collection.extend({
     // 
     // @property {Function} renderer: a function to render the data for this field.
     // Signature: function(value, field, doc) where value is the value of this
    -// cell, field is corresponding field object and document is the document
    +// cell, field is corresponding field object and record is the record
     // object. Note that implementing functions can ignore arguments (e.g.
     // function(value) would be a valid formatter function).
     // 
     // @property {Function} deriver: a function to derive/compute the value of data
     // in this field as a function of this field's value (if any) and the current
    -// document, its signature and behaviour is the same as for renderer.  Use of
    +// record, its signature and behaviour is the same as for renderer.  Use of
     // this function allows you to define an entirely new value for data in this
     // field. This provides support for a) 'derived/computed' fields: i.e. fields
     // whose data are functions of the data in other fields b) transforming the
    @@ -461,7 +461,7 @@ my.Query = Backbone.Model.extend({
     //   "_type" : "terms",
     //   // total number of tokens in the facet
     //   "total": 5,
    -//   // @property {number} number of documents which have no value for the field
    +//   // @property {number} number of records which have no value for the field
     //   "missing" : 0,
     //   // number of facet values not included in the returned facets
     //   "other": 0,
    diff --git a/src/view-graph.js b/src/view-graph.js
    index ff3cf3b9..e7059cac 100644
    --- a/src/view-graph.js
    +++ b/src/view-graph.js
    @@ -97,8 +97,8 @@ my.Graph = Backbone.View.extend({
         this.model.bind('change', this.render);
         this.model.fields.bind('reset', this.render);
         this.model.fields.bind('add', this.render);
    -    this.model.currentDocuments.bind('add', this.redraw);
    -    this.model.currentDocuments.bind('reset', this.redraw);
    +    this.model.currentRecords.bind('add', this.redraw);
    +    this.model.currentRecords.bind('reset', this.redraw);
         // because we cannot redraw when hidden we may need when becoming visible
         this.bind('view:show', function() {
           if (this.needToRedraw) {
    @@ -181,7 +181,7 @@ my.Graph = Backbone.View.extend({
         //   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 ((!areWeVisible || this.model.currentDocuments.length === 0)) {
    +    if ((!areWeVisible || this.model.currentRecords.length === 0)) {
           this.needToRedraw = true;
           return;
         }
    @@ -209,8 +209,8 @@ my.Graph = Backbone.View.extend({
         // However, that is non-trivial to work out from a dataset (datasets may
         // have no field type info). Thus at present we only do this for bars.
         var tickFormatter = function (val) {
    -      if (self.model.currentDocuments.models[val]) {
    -        var out = self.model.currentDocuments.models[val].get(self.state.attributes.group);
    +      if (self.model.currentRecords.models[val]) {
    +        var out = self.model.currentRecords.models[val].get(self.state.attributes.group);
             // if the value was in fact a number we want that not the 
             if (typeof(out) == 'number') {
               return val;
    @@ -266,7 +266,7 @@ my.Graph = Backbone.View.extend({
               tickLength: 1,
               tickFormatter: tickFormatter,
               min: -0.5,
    -          max: self.model.currentDocuments.length - 0.5
    +          max: self.model.currentRecords.length - 0.5
             }
           }
         };
    @@ -304,8 +304,8 @@ my.Graph = Backbone.View.extend({
                 y = _tmp;
               }
               // convert back from 'index' value on x-axis (e.g. in cases where non-number values)
    -          if (self.model.currentDocuments.models[x]) {
    -            x = self.model.currentDocuments.models[x].get(self.state.attributes.group);
    +          if (self.model.currentRecords.models[x]) {
    +            x = self.model.currentRecords.models[x].get(self.state.attributes.group);
               } else {
                 x = x.toFixed(2);
               }
    @@ -339,7 +339,7 @@ my.Graph = Backbone.View.extend({
         var series = [];
         _.each(this.state.attributes.series, function(field) {
           var points = [];
    -      _.each(self.model.currentDocuments.models, function(doc, index) {
    +      _.each(self.model.currentRecords.models, function(doc, index) {
             var xfield = self.model.fields.get(self.state.attributes.group);
             var x = doc.getFieldValue(xfield);
             // time series
    diff --git a/src/view-grid.js b/src/view-grid.js
    index fc9400b9..03213cd1 100644
    --- a/src/view-grid.js
    +++ b/src/view-grid.js
    @@ -17,9 +17,9 @@ my.Grid = Backbone.View.extend({
         var self = this;
         this.el = $(this.el);
         _.bindAll(this, 'render', 'onHorizontalScroll');
    -    this.model.currentDocuments.bind('add', this.render);
    -    this.model.currentDocuments.bind('reset', this.render);
    -    this.model.currentDocuments.bind('remove', this.render);
    +    this.model.currentRecords.bind('add', this.render);
    +    this.model.currentRecords.bind('reset', this.render);
    +    this.model.currentRecords.bind('remove', this.render);
         this.tempState = {};
         var state = _.extend({
             hiddenFields: []
    @@ -77,13 +77,13 @@ my.Grid = Backbone.View.extend({
           showColumn: function() { self.showColumn(e); },
           deleteRow: function() {
             var self = this;
    -        var doc = _.find(self.model.currentDocuments.models, function(doc) {
    +        var doc = _.find(self.model.currentRecords.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.tempState.currentRow;
             });
             doc.destroy().then(function() { 
    -            self.model.currentDocuments.remove(doc);
    +            self.model.currentRecords.remove(doc);
                 self.trigger('recline:flash', {message: "Row deleted successfully"});
               }).fail(function(err) {
                 self.trigger('recline:flash', {message: "Errorz! " + err});
    @@ -213,7 +213,7 @@ my.Grid = Backbone.View.extend({
         });
         var htmls = Mustache.render(this.template, this.toTemplateJSON());
         this.el.html(htmls);
    -    this.model.currentDocuments.forEach(function(doc) {
    +    this.model.currentRecords.forEach(function(doc) {
           var tr = $('');
           self.el.find('tbody').append(tr);
           var newView = new my.GridRow({
    @@ -246,7 +246,7 @@ my.Grid = Backbone.View.extend({
       }
     });
     
    -// ## GridRow View for rendering an individual document.
    +// ## GridRow View for rendering an individual record.
     //
     // Since we want this to update in place it is up to creator to provider the element to attach to.
     //
    @@ -256,7 +256,7 @@ my.Grid = Backbone.View.extend({
     //
     // 
     // var row = new GridRow({
    -//   model: dataset-document,
    +//   model: dataset-record,
     //     el: dom-element,
     //     fields: mydatasets.fields // a FieldList object
     //   });
    diff --git a/src/view-map.js b/src/view-map.js
    index 8e622a0a..f0a75ae8 100644
    --- a/src/view-map.js
    +++ b/src/view-map.js
    @@ -7,7 +7,7 @@ this.recline.View = this.recline.View || {};
     
     // ## Map view for a Dataset using Leaflet mapping library.
     //
    -// This view allows to plot gereferenced documents on a map. The location
    +// This view allows to plot gereferenced records on a map. The location
     // information can be provided either via a field with
     // [GeoJSON](http://geojson.org) objects or two fields with latitude and
     // longitude coordinates.
    @@ -115,14 +115,14 @@ my.Map = Backbone.View.extend({
           self.render()
         });
     
    -    // Listen to changes in the documents
    -    this.model.currentDocuments.bind('add', function(doc){self.redraw('add',doc)});
    -    this.model.currentDocuments.bind('change', function(doc){
    +    // Listen to changes in the records
    +    this.model.currentRecords.bind('add', function(doc){self.redraw('add',doc)});
    +    this.model.currentRecords.bind('change', function(doc){
             self.redraw('remove',doc);
             self.redraw('add',doc);
         });
    -    this.model.currentDocuments.bind('remove', function(doc){self.redraw('remove',doc)});
    -    this.model.currentDocuments.bind('reset', function(){self.redraw('reset')});
    +    this.model.currentRecords.bind('remove', function(doc){self.redraw('remove',doc)});
    +    this.model.currentRecords.bind('reset', function(){self.redraw('reset')});
     
         this.bind('view:show',function(){
           // If the div was hidden, Leaflet needs to recalculate some sizes
    @@ -184,9 +184,9 @@ my.Map = Backbone.View.extend({
       // Actions can be:
       //
       // * reset: Clear all features
    -  // * add: Add one or n features (documents)
    -  // * remove: Remove one or n features (documents)
    -  // * refresh: Clear existing features and add all current documents
    +  // * add: Add one or n features (records)
    +  // * remove: Remove one or n features (records)
    +  // * refresh: Clear existing features and add all current records
       redraw: function(action, doc){
         var self = this;
         action = action || 'refresh';
    @@ -201,7 +201,7 @@ my.Map = Backbone.View.extend({
         if (this.geomReady && this.mapReady){
           if (action == 'reset' || action == 'refresh'){
             this.features.clearLayers();
    -        this._add(this.model.currentDocuments.models);
    +        this._add(this.model.currentRecords.models);
           } else if (action == 'add' && doc){
             this._add(doc);
           } else if (action == 'remove' && doc){
    @@ -266,11 +266,11 @@ my.Map = Backbone.View.extend({
     
       // Private: Add one or n features to the map
       //
    -  // For each document passed, a GeoJSON geometry will be extracted and added
    +  // For each record passed, a GeoJSON geometry will be extracted and added
       // to the features layer. If an exception is thrown, the process will be
       // stopped and an error notification shown.
       //
    -  // Each feature will have a popup associated with all the document fields.
    +  // Each feature will have a popup associated with all the record fields.
       //
       _add: function(docs){
         var self = this;
    @@ -281,7 +281,7 @@ my.Map = Backbone.View.extend({
         var wrongSoFar = 0;
         _.every(docs,function(doc){
           count += 1;
    -      var feature = self._getGeometryFromDocument(doc);
    +      var feature = self._getGeometryFromRecord(doc);
           if (typeof feature === 'undefined' || feature === null){
             // Empty field
             return true;
    @@ -338,9 +338,9 @@ my.Map = Backbone.View.extend({
     
       },
     
    -  // Private: Return a GeoJSON geomtry extracted from the document fields
    +  // Private: Return a GeoJSON geomtry extracted from the record fields
       //
    -  _getGeometryFromDocument: function(doc){
    +  _getGeometryFromRecord: function(doc){
         if (this.geomReady){
           if (this.state.get('geomField')){
             var value = doc.get(this.state.get('geomField'));
    diff --git a/src/view-slickgrid.js b/src/view-slickgrid.js
    index f42e905e..efe34eac 100644
    --- a/src/view-slickgrid.js
    +++ b/src/view-slickgrid.js
    @@ -19,9 +19,9 @@ my.SlickGrid = Backbone.View.extend({
         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.model.currentRecords.bind('add', this.render);
    +    this.model.currentRecords.bind('reset', this.render);
    +    this.model.currentRecords.bind('remove', this.render);
     
         var state = _.extend({
             hiddenColumns: [],
    @@ -110,7 +110,7 @@ my.SlickGrid = Backbone.View.extend({
     
         var data = [];
     
    -    this.model.currentDocuments.each(function(doc){
    +    this.model.currentRecords.each(function(doc){
           var row = {};
           self.model.fields.each(function(field){
             row[field.id] = doc.getFieldValue(field);
    diff --git a/src/view-timeline.js b/src/view-timeline.js
    index 18ad35ef..9d98d371 100644
    --- a/src/view-timeline.js
    +++ b/src/view-timeline.js
    @@ -31,7 +31,7 @@ my.Timeline = Backbone.View.extend({
         this.model.fields.bind('reset', function() {
           self._setupTemporalField();
         });
    -    this.model.currentDocuments.bind('all', function() {
    +    this.model.currentRecords.bind('all', function() {
           self.reloadData();
         });
         var stateData = _.extend({
    @@ -78,11 +78,11 @@ my.Timeline = Backbone.View.extend({
             ]
           }
         };
    -    this.model.currentDocuments.each(function(doc) {
    +    this.model.currentRecords.each(function(doc) {
           var start = doc.get(self.state.get('startField'));
           if (start) {
    -        var end = moment(doc.get(self.state.get('endField')));
    -        end = end ? end.toDate() : null;
    +        var end = doc.get(self.state.get('endField'));
    +        end = end ? moment(end).toDate() : null;
             var tlEntry = {
               "startDate": moment(start).toDate(),
               "endDate": end,
    diff --git a/src/view-transform-dialog.js b/src/view-transform-dialog.js
    index e4bee9ae..a0ae741f 100644
    --- a/src/view-transform-dialog.js
    +++ b/src/view-transform-dialog.js
    @@ -98,7 +98,7 @@ my.ColumnTransform = Backbone.View.extend({
         }
         this.el.modal('hide');
         this.trigger('recline:flash', {message: "Updating all visible docs. This could take a while...", persist: true, loader: true});
    -      var docs = self.model.currentDocuments.map(function(doc) {
    +      var docs = self.model.currentRecords.map(function(doc) {
            return doc.toJSON();
           });
         // TODO: notify about failed docs? 
    @@ -107,14 +107,14 @@ my.ColumnTransform = Backbone.View.extend({
         function onCompletedUpdate() {
           totalToUpdate += -1;
           if (totalToUpdate === 0) {
    -        self.trigger('recline:flash', {message: toUpdate.length + " documents updated successfully"});
    +        self.trigger('recline:flash', {message: toUpdate.length + " records updated successfully"});
             alert('WARNING: We have only updated the docs in this view. (Updating of all docs not yet implemented!)');
             self.remove();
           }
         }
         // TODO: Very inefficient as we search through all docs every time!
         _.each(toUpdate, function(editedDoc) {
    -      var realDoc = self.model.currentDocuments.get(editedDoc.id);
    +      var realDoc = self.model.currentRecords.get(editedDoc.id);
           realDoc.set(editedDoc);
           realDoc.save().then(onCompletedUpdate).fail(onCompletedUpdate);
         });
    @@ -158,7 +158,7 @@ my.ColumnTransform = Backbone.View.extend({
           var editFunc = costco.evalFunction(e.target.value);
           if (!editFunc.errorMessage) {
             errors.text('No syntax error.');
    -        var docs = self.model.currentDocuments.map(function(doc) {
    +        var docs = self.model.currentRecords.map(function(doc) {
               return doc.toJSON();
             });
             var previewData = costco.previewTransform(docs, editFunc, self.state.currentColumn);
    diff --git a/test/backend.elasticsearch.test.js b/test/backend.elasticsearch.test.js
    index c0a19831..0c08607f 100644
    --- a/test/backend.elasticsearch.test.js
    +++ b/test/backend.elasticsearch.test.js
    @@ -155,11 +155,11 @@ test("write", function() {
       stop();
     
       var id = parseInt(Math.random()*100000000).toString();
    -  var doc = {
    +  var rec = {
         id: id,
         title: 'my title'
       };
    -  var jqxhr = backend.upsert(doc);
    +  var jqxhr = backend.upsert(rec);
       jqxhr.done(function(data) {
         ok(data.ok);
         equal(data._id, id);
    @@ -167,16 +167,16 @@ test("write", function() {
         equal(data._version, 1);
         
         // update
    -    doc.title = 'new title';
    -    var jqxhr = backend.upsert(doc);
    +    rec.title = 'new title';
    +    var jqxhr = backend.upsert(rec);
         jqxhr.done(function(data) {
           equal(data._version, 2);
     
           // delete
    -      var jqxhr = backend.delete(doc.id);
    +      var jqxhr = backend.delete(rec.id);
           jqxhr.done(function(data) {
             ok(data.ok);
    -        doc = null;
    +        rec = null;
     
             // try to get ...
             var jqxhr = backend.get(id);
    @@ -233,10 +233,10 @@ test("query", function() {
     
       dataset.fetch().done(function(dataset) {
         deepEqual(['_created', '_last_modified', 'end', 'owner', 'start', 'title'], _.pluck(dataset.fields.toJSON(), 'id'));
    -    dataset.query().then(function(docList) {
    +    dataset.query().then(function(recList) {
           equal(3, dataset.docCount);
    -      equal(3, docList.length);
    -      equal('Note 1', docList.models[0].get('title'));
    +      equal(3, recList.length);
    +      equal('Note 1', recList.models[0].get('title'));
           start();
         });
       });
    @@ -254,14 +254,14 @@ test("write", function() {
       stop();
     
       var id = parseInt(Math.random()*100000000).toString();
    -  var doc = new recline.Model.Document({
    +  var rec = new recline.Model.Record({
         id: id,
         title: 'my title'
       });
    -  doc.backend = backend;
    -  doc.dataset = dataset;
    -  dataset.currentDocuments.add(doc);
    -  var jqxhr = doc.save();
    +  rec.backend = backend;
    +  rec.dataset = dataset;
    +  dataset.currentRecords.add(rec);
    +  var jqxhr = rec.save();
       jqxhr.done(function(data) {
         ok(data.ok);
         equal(data._id, id);
    @@ -269,29 +269,29 @@ test("write", function() {
         equal(data._version, 1);
         
         // update
    -    doc.set({title: 'new title'});
    -    var jqxhr = doc.save();
    +    rec.set({title: 'new title'});
    +    var jqxhr = rec.save();
         jqxhr.done(function(data) {
           equal(data._version, 2);
     
           // delete
    -      var jqxhr = doc.destroy();
    +      var jqxhr = rec.destroy();
           jqxhr.done(function(data) {
             ok(data.ok);
    -        doc = null;
    +        rec = null;
     
             // try to get ...
    -        var olddoc = new recline.Model.Document({id: id});
    -        equal(olddoc.get('title'), null);
    -        olddoc.dataset = dataset;
    -        olddoc.backend = backend;
    -        var jqxhr = olddoc.fetch();
    +        var oldrec = new recline.Model.Record({id: id});
    +        equal(oldrec.get('title'), null);
    +        oldrec.dataset = dataset;
    +        oldrec.backend = backend;
    +        var jqxhr = oldrec.fetch();
             jqxhr.done(function(data) {
               // should not be here
               ok(false, 'Should have got 404');
             }).error(function(error) {
               equal(error.status, 404);
    -          equal(typeof olddoc.get('title'), 'undefined');
    +          equal(typeof oldrec.get('title'), 'undefined');
               start();
             });
           });
    diff --git a/test/backend/csv.test.js b/test/backend/csv.test.js
    index e47c46bf..fc944252 100644
    --- a/test/backend/csv.test.js
    +++ b/test/backend/csv.test.js
    @@ -26,7 +26,7 @@ test("parseCSV", function() {
       '"Other, AN", 12:35\n';
       var dataset = recline.Backend.CSV.csvToDataset(csv);
       dataset.query();
    -  equal(dataset.currentDocuments.length, 3);
    +  equal(dataset.currentRecords.length, 3);
     });
     
     test("parseCSVsemicolon", function() {
    diff --git a/test/backend/memory.test.js b/test/backend/memory.test.js
    index 20ce290a..06e32e77 100644
    --- a/test/backend/memory.test.js
    +++ b/test/backend/memory.test.js
    @@ -30,8 +30,8 @@ test('query', function () {
         , from: 2
       };
       var out = data.query(queryObj);
    -  deepEqual(out.documents[0], memoryData[2]);
    -  equal(out.documents.length, 4);
    +  deepEqual(out.records[0], memoryData[2]);
    +  equal(out.records.length, 4);
       equal(out.total, 6);
     });
     
    @@ -43,18 +43,18 @@ test('query sort', function () {
         ]
       };
       var out = data.query(queryObj);
    -  equal(out.documents[0].x, 6);
    +  equal(out.records[0].x, 6);
     });
     
     test('query string', function () {
       var data = _wrapData();
       var out = data.query({q: 'UK'});
       equal(out.total, 3);
    -  deepEqual(_.pluck(out.documents, 'country'), ['UK', 'UK', 'UK']);
    +  deepEqual(_.pluck(out.records, 'country'), ['UK', 'UK', 'UK']);
     
       var out = data.query({q: 'UK 6'})
       equal(out.total, 1);
    -  deepEqual(out.documents[0].id, 1);
    +  deepEqual(out.records[0].id, 1);
     });
     
     test('filters', function () {
    @@ -63,7 +63,7 @@ test('filters', function () {
       query.addTermFilter('country', 'UK');
       var out = data.query(query.toJSON());
       equal(out.total, 3);
    -  deepEqual(_.pluck(out.documents, 'country'), ['UK', 'UK', 'UK']);
    +  deepEqual(_.pluck(out.records, 'country'), ['UK', 'UK', 'UK']);
     });
     
     test('facet', function () {
    @@ -118,7 +118,7 @@ var memoryData = {
         , id: 'test-dataset'
       },
       fields: [{id: 'x'}, {id: 'y'}, {id: 'z'}, {id: 'country'}, {id: 'label'}],
    -  documents: [
    +  records: [
         {id: 0, x: 1, y: 2, z: 3, country: 'DE', label: 'first'}
         , {id: 1, x: 2, y: 4, z: 6, country: 'UK', label: 'second'}
         , {id: 2, x: 3, y: 6, z: 9, country: 'US', label: 'third'}
    @@ -129,16 +129,16 @@ var memoryData = {
     };
     
     function makeBackendDataset() {
    -  var dataset = new recline.Backend.Memory.createDataset(memoryData.documents, null, memoryData.metadata);
    +  var dataset = new recline.Backend.Memory.createDataset(memoryData.records, null, memoryData.metadata);
       return dataset;
     }
     
     test('createDataset', function () {
    -  var dataset = recline.Backend.Memory.createDataset(memoryData.documents);
    +  var dataset = recline.Backend.Memory.createDataset(memoryData.records);
       equal(dataset.fields.length, 6);
       deepEqual(['id', 'x', 'y', 'z', 'country', 'label'], dataset.fields.pluck('id'));
       dataset.query();
    -  equal(memoryData.documents.length, dataset.currentDocuments.length);
    +  equal(memoryData.records.length, dataset.currentRecords.length);
     });
     
     test('basics', function () {
    @@ -162,8 +162,8 @@ test('query', function () {
         size: 4
         , from: 2
       };
    -  dataset.query(queryObj).then(function(documentList) {
    -    deepEqual(data[2], documentList.models[0].toJSON());
    +  dataset.query(queryObj).then(function(recordList) {
    +    deepEqual(data[2], recordList.models[0].toJSON());
       });
     });
     
    @@ -177,7 +177,7 @@ test('query sort', function () {
         ]
       };
       dataset.query(queryObj).then(function() {
    -    var doc0 = dataset.currentDocuments.models[0].toJSON();
    +    var doc0 = dataset.currentRecords.models[0].toJSON();
         equal(doc0.x, 6);
       });
     });
    @@ -186,13 +186,13 @@ test('query string', function () {
       var dataset = makeBackendDataset();
       dataset.fetch();
       dataset.query({q: 'UK'}).then(function() {
    -    equal(dataset.currentDocuments.length, 3);
    -    deepEqual(dataset.currentDocuments.pluck('country'), ['UK', 'UK', 'UK']);
    +    equal(dataset.currentRecords.length, 3);
    +    deepEqual(dataset.currentRecords.pluck('country'), ['UK', 'UK', 'UK']);
       });
     
       dataset.query({q: 'UK 6'}).then(function() {
    -    equal(dataset.currentDocuments.length, 1);
    -    deepEqual(dataset.currentDocuments.models[0].id, 1);
    +    equal(dataset.currentRecords.length, 1);
    +    deepEqual(dataset.currentRecords.models[0].id, 1);
       });
     });
     
    @@ -200,8 +200,8 @@ test('filters', function () {
       var dataset = makeBackendDataset();
       dataset.queryState.addTermFilter('country', 'UK');
       dataset.query().then(function() {
    -    equal(dataset.currentDocuments.length, 3);
    -    deepEqual(dataset.currentDocuments.pluck('country'), ['UK', 'UK', 'UK']);
    +    equal(dataset.currentRecords.length, 3);
    +    deepEqual(dataset.currentRecords.pluck('country'), ['UK', 'UK', 'UK']);
       });
     });
     
    @@ -247,7 +247,7 @@ test('update and delete', function () {
         // Test Delete
         doc1.destroy().then(function() {
           equal(data.data.length, 5);
    -      equal(data.data[0].x, memoryData.documents[1].x);
    +      equal(data.data[0].x, memoryData.records[1].x);
         });
       });
     });
    diff --git a/test/model.test.js b/test/model.test.js
    index fef946e7..90d2e6d7 100644
    --- a/test/model.test.js
    +++ b/test/model.test.js
    @@ -39,7 +39,7 @@ test('Field: basics', function () {
     });
     
     test('Field: default renderers', function () {
    -  var doc = new recline.Model.Document({
    +  var doc = new recline.Model.Record({
         x: 12.3,
         myobject: {a: 1, b: 2},
         link: 'http://abc.com/',
    @@ -74,7 +74,7 @@ test('Field: default renderers', function () {
     });
     
     test('Field: custom deriver and renderer', function () {
    -  var doc = new recline.Model.Document({x: 123});
    +  var doc = new recline.Model.Record({x: 123});
       var cellRenderer = function(value, field) {
         return '' + value + '';
       }
    diff --git a/test/view-grid.test.js b/test/view-grid.test.js
    index 4d2d0a96..d455173c 100644
    --- a/test/view-grid.test.js
    +++ b/test/view-grid.test.js
    @@ -38,7 +38,7 @@ test('state', function () {
     test('new GridRow View', function () {
       var $el = $('');
       $('.fixtures .test-datatable').append($el);
    -  var doc = new recline.Model.Document({
    +  var doc = new recline.Model.Record({
         'id': 1,
         'b': '2',
         'a': '1'
    diff --git a/test/view-map.test.js b/test/view-map.test.js
    index adc12940..3ad5a4d2 100644
    --- a/test/view-map.test.js
    +++ b/test/view-map.test.js
    @@ -11,12 +11,12 @@ var GeoJSONFixture = {
             {id: 'z'},
             {id: 'geom'}
           ];
    -    var documents = [
    +    var records = [
           {id: 0, x: 1, y: 2, z: 3, geom: '{"type":"Point","coordinates":[13.40,52.35]}'},
           {id: 1, x: 2, y: 4, z: 6, geom: {type:"Point",coordinates:[13.40,52.35]}},
           {id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}
         ];
    -    var dataset = recline.Backend.Memory.createDataset(documents, fields);
    +    var dataset = recline.Backend.Memory.createDataset(records, fields);
         return dataset;
       }
     };
    @@ -55,12 +55,12 @@ test('Lat/Lon geom fields', function () {
       // Check that all markers were created
       equal(_getFeaturesCount(view.features),6);
     
    -  // Delete a document
    -  view.model.currentDocuments.remove(view.model.currentDocuments.get('1'));
    +  // Delete a record
    +  view.model.currentRecords.remove(view.model.currentRecords.get('1'));
       equal(_getFeaturesCount(view.features),5);
     
       // Add a new one
    -  view.model.currentDocuments.add({id: 7, x: 7, y: 14, z: 21, country: 'KX', label: 'seventh', lat:13.23, lon:23.56}),
    +  view.model.currentRecords.add({id: 7, x: 7, y: 14, z: 21, country: 'KX', label: 'seventh', lat:13.23, lon:23.56}),
       equal(_getFeaturesCount(view.features),6);
     
       view.remove();
    @@ -79,12 +79,12 @@ test('GeoJSON geom field', function () {
       // Check that all features were created
       equal(_getFeaturesCount(view.features),3);
     
    -  // Delete a document
    -  view.model.currentDocuments.remove(view.model.currentDocuments.get('2'));
    +  // Delete a record
    +  view.model.currentRecords.remove(view.model.currentRecords.get('2'));
       equal(_getFeaturesCount(view.features),2);
     
       // Add it back
    -  view.model.currentDocuments.add({id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}),
    +  view.model.currentRecords.add({id: 2, x: 3, y: 6, z: 9, geom: {type:"LineString",coordinates:[[100.0, 0.0],[101.0, 1.0]]}}),
       equal(_getFeaturesCount(view.features),3);
     
       view.remove();
    diff --git a/test/view-slickgrid.test.js b/test/view-slickgrid.test.js
    index 56b5a457..1b3448cc 100644
    --- a/test/view-slickgrid.test.js
    +++ b/test/view-slickgrid.test.js
    @@ -16,7 +16,7 @@ test('basic', function () {
       assertPresent('.slick-header-column[title="x"]');
       equal($('.slick-header-column').length,dataset.fields.length);
     
    -  equal(dataset.currentDocuments.length,view.grid.getDataLength());
    +  equal(dataset.currentRecords.length,view.grid.getDataLength());
     
       view.remove();
     });
    diff --git a/test/view-timeline.test.js b/test/view-timeline.test.js
    index 562fc982..583afc79 100644
    --- a/test/view-timeline.test.js
    +++ b/test/view-timeline.test.js
    @@ -17,16 +17,16 @@ test('extract dates and timelineJSON', function () {
             'headline': '',
             'date': [
               {
    -            'startDate': '2012-03-20',
    +            'startDate': new Date('2012-03-20'),
                 'endDate': null,
    -            'headline': '2012-03-20',
    -            'text': ''
    +            'headline': '1',
    +            'text': '
    Date: 2012-03-20
    title: 1
    ' }, { - 'startDate': '2012-03-25', + 'startDate': new Date('2012-03-25'), 'endDate': null, - 'headline': '2012-03-25', - 'text': '' + 'headline': '2', + 'text': '
    Date: 2012-03-25
    title: 2
    ' } ] } From 53327a7a1ef68cb0505d69cedabdfe8cac2267b1 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 31 May 2012 21:36:52 +0100 Subject: [PATCH 2/8] [#124,refactor,view][s]: ([m] in effect) rename DataExplorer view to MultiView and split contents view.js into separate files - fixes #124. --- app/index.html | 7 +- app/js/app.js | 2 +- library-view.markdown | 100 ++++++++++++++++++ library.html | 4 +- make | 2 +- src/{view.js => view.multiview.js} | 24 ++--- src/widget.facetviewer.js | 80 ++++++++++++++ src/widget.queryeditor.js | 65 ++++++++++++ test/index.html | 6 +- test/{view.test.js => view.multiview.test.js} | 10 +- 10 files changed, 275 insertions(+), 25 deletions(-) create mode 100644 library-view.markdown rename src/{view.js => view.multiview.js} (97%) create mode 100644 src/widget.facetviewer.js create mode 100644 src/widget.queryeditor.js rename test/{view.test.js => view.multiview.test.js} (89%) diff --git a/app/index.html b/app/index.html index 0c56105f..4b16c85a 100644 --- a/app/index.html +++ b/app/index.html @@ -58,13 +58,16 @@ - + + - + + + diff --git a/app/js/app.js b/app/js/app.js index 990be82a..7de2ee58 100755 --- a/app/js/app.js +++ b/app/js/app.js @@ -108,7 +108,7 @@ var ExplorerApp = Backbone.View.extend({ } ]; - this.dataExplorer = new recline.View.DataExplorer({ + this.dataExplorer = new recline.View.MultiView({ model: dataset, el: $el, state: state, diff --git a/library-view.markdown b/library-view.markdown new file mode 100644 index 00000000..96ab9353 --- /dev/null +++ b/library-view.markdown @@ -0,0 +1,100 @@ +--- +layout: container +title: Library - Views +--- + + + +Recline Views are instances of Backbone Views and they act as 'WUI' (web user +interface) component displaying some model object in the DOM. Like all Backbone +views they have a pointer to a model (or a collection) and have an associated +DOM-style element (usually this element will be bound into the page at some +point). + +Views provided by core Recline are crudely divided into two types: + +* Dataset Views: a View intended for displaying a recline.Model.Dataset in some + fashion. Examples are the Grid, Graph and Map views. +* Widget Views: a widget used for displaying some specific (and smaller) aspect + of a dataset or the application. Examples are QueryEditor and FilterEditor + which both provide a way for editing (a part of) a `recline.Model.Query` + associated to a Dataset. + +## Dataset View + +These views are just Backbone views with a few additional conventions: + +1. The model passed to the View should always be a recline.Model.Dataset + instance +2. Views should generate their own root element rather than having it passed + in. +3. Views should apply a css class named 'recline-{view-name-lower-cased} to the + root element (and for all CSS for this view to be qualified using this CSS + class) +4. Read-only mode: CSS for this view should respect/utilize a parent + recline-read-only class in order to trigger read-only behaviour (this class + will usually be set on some parent element of the view's root element). +5. State: state (configuration) information for the view should be stored on an + attribute named state that is an instance of a Backbone Model (or, more + speficially, be an instance of `recline.Model.ObjectState`). In addition, a + state attribute may be specified in the Hash passed to a View on + iniitialization and this information should be used to set the initial state + of the view. + + Example of state would be the set of fields being plotted in a graph view. + + More information about State can be found below. + +To summarize some of this, the initialize function for a Dataset View should +look like: + +
    +   initialize: {
    +       model: {a recline.Model.Dataset instance}
    +       // el: {do not specify - instead view should create}
    +       state: {(optional) Object / Hash specifying initial state}
    +       ...
    +   }
    +
    + +Note: Dataset Views in core Recline have a common layout on disk as follows, +where ViewName is the named of View class: + +
    +src/view-{lower-case-ViewName}.js
    +css/{lower-case-ViewName}.css
    +test/view-{lower-case-ViewName}.js
    +
    + +### State + +State information exists in order to support state serialization into the url +or elsewhere and reloading of application from a stored state. + +State is available not only for individual views (as described above) but for +the dataset (e.g. the current query). For an example of pulling together state +from across multiple components see `recline.View.DataExplorer`. + +### Flash Messages / Notifications + +To send 'flash messages' or notifications the convention is that views should +fire an event named `recline:flash` with a payload that is a flash object with +the following attributes (all optional): + +* message: message to show. +* category: warning (default), success, error +* persist: if true alert is persistent, o/w hidden after 3s (default=false) +* loader: if true show a loading message + +Objects or views wishing to bind to flash messages may then subscribe to these +events and take some action such as displaying them to the user. For an example +of such behaviour see the DataExplorer view. + +### Writing your own Views + +See the existing Views. + diff --git a/library.html b/library.html index 3593bbc9..850afcf3 100644 --- a/library.html +++ b/library.html @@ -112,7 +112,7 @@ title: Library - Home

    Complementing the model are various Views (you can also easily write your own). Each view holds a pointer to a Dataset:

      -
    • DataExplorer: the parent view which manages the overall app and sets up +
    • MultiView: the parent view which manages the overall app and sets up sub views.
    • Grid: the data grid view.
    • Graph: a simple graphing view using Models and Views (Widgets)
      • Models
      • -
      • DataExplorer View (plus common view code)
      • +
      • MultiView View (plus common view code)
      • (Data) Grid View
      • Graph View (based on Flot)
      • Map View (based on Leaflet)
      • diff --git a/make b/make index 97feeeaf..860faf54 100755 --- a/make +++ b/make @@ -13,7 +13,7 @@ def docs(): print("** Building docs") docco_executable = os.environ.get('DOCCO_EXECUTABLE','docco') - cmd = '%s src/model.js src/view.js src/view-grid.js src/view-graph.js src/view-map.js' % (docco_executable) + cmd = '%s src/*.js' % (docco_executable) os.system(cmd) if os.path.exists('/tmp/recline-docs'): shutil.rmtree('/tmp/recline-docs') diff --git a/src/view.js b/src/view.multiview.js similarity index 97% rename from src/view.js rename to src/view.multiview.js index c49b6592..78f74372 100644 --- a/src/view.js +++ b/src/view.multiview.js @@ -70,7 +70,7 @@ // // State is available not only for individual views (as described above) but // for the dataset (e.g. the current query). For an example of pulling together -// state from across multiple components see `recline.View.DataExplorer`. +// state from across multiple components see `recline.View.MultiView`. // // ### Flash Messages / Notifications // @@ -85,7 +85,7 @@ // // Objects or views wishing to bind to flash messages may then subscribe to // these events and take some action such as displaying them to the user. For -// an example of such behaviour see the DataExplorer view. +// an example of such behaviour see the MultiView view. // // ### Writing your own Views // @@ -98,12 +98,12 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { -// ## DataExplorer +// ## MultiView // -// The primary view for the entire application. Usage: +// Manage multiple views together along with query editor etc. Usage: // //
        -// var myExplorer = new model.recline.DataExplorer({
        +// var myExplorer = new model.recline.MultiView({
         //   model: {{recline.Model.Dataset instance}}
         //   el: {{an existing dom element}}
         //   views: {{dataset views}}
        @@ -120,7 +120,7 @@ this.recline.View = this.recline.View || {};
         // Graph).
         //
         // **views**: (optional) the dataset views (Grid, Graph etc) for
        -// DataExplorer to show. This is an array of view hashes. If not provided
        +// MultiView to show. This is an array of view hashes. If not provided
         // initialize with (recline.View.)Grid, Graph, and Map views (with obvious id
         // and labels!).
         //
        @@ -161,8 +161,8 @@ this.recline.View = this.recline.View || {};
         // Note that at present we do *not* serialize information about the actual set
         // of views in use -- e.g. those specified by the views argument -- but instead 
         // expect either that the default views are fine or that the client to have
        -// initialized the DataExplorer with the relevant views themselves.
        -my.DataExplorer = Backbone.View.extend({
        +// initialized the MultiView with the relevant views themselves.
        +my.MultiView = Backbone.View.extend({
           template: ' \
           
        \
        \ @@ -453,12 +453,12 @@ my.DataExplorer = Backbone.View.extend({ } }); -// ### DataExplorer.restore +// ### MultiView.restore // -// Restore a DataExplorer instance from a serialized state including the associated dataset -my.DataExplorer.restore = function(state) { +// Restore a MultiView instance from a serialized state including the associated dataset +my.MultiView.restore = function(state) { var dataset = recline.Model.Dataset.restore(state); - var explorer = new my.DataExplorer({ + var explorer = new my.MultiView({ model: dataset, state: state }); diff --git a/src/widget.facetviewer.js b/src/widget.facetviewer.js new file mode 100644 index 00000000..0bdd52cd --- /dev/null +++ b/src/widget.facetviewer.js @@ -0,0 +1,80 @@ +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + +my.FacetViewer = Backbone.View.extend({ + className: 'recline-facet-viewer well', + template: ' \ + × \ +
        \ +
        \ +

        Facets

        \ +
        \ + {{#facets}} \ + \ + {{/facets}} \ +
        \ + ', + + events: { + 'click .js-hide': 'onHide', + 'click .js-facet-filter': 'onFacetFilter' + }, + initialize: function(model) { + _.bindAll(this, 'render'); + this.el = $(this.el); + this.model.facets.bind('all', this.render); + this.model.fields.bind('all', this.render); + this.render(); + }, + render: function() { + var tmplData = { + facets: this.model.facets.toJSON(), + fields: this.model.fields.toJSON() + }; + tmplData.facets = _.map(tmplData.facets, function(facet) { + if (facet._type === 'date_histogram') { + facet.entries = _.map(facet.entries, function(entry) { + entry.term = new Date(entry.time).toDateString(); + return entry; + }); + } + return facet; + }); + var templated = Mustache.render(this.template, tmplData); + this.el.html(templated); + // are there actually any facets to show? + if (this.model.facets.length > 0) { + this.el.show(); + } else { + this.el.hide(); + } + }, + onHide: function(e) { + e.preventDefault(); + this.el.hide(); + }, + onFacetFilter: function(e) { + var $target= $(e.target); + var fieldId = $target.closest('.facet-summary').attr('data-facet'); + var value = $target.attr('data-value'); + this.model.queryState.addTermFilter(fieldId, value); + } +}); + + +})(jQuery, recline.View); + diff --git a/src/widget.queryeditor.js b/src/widget.queryeditor.js new file mode 100644 index 00000000..964ff97d --- /dev/null +++ b/src/widget.queryeditor.js @@ -0,0 +1,65 @@ +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + +my.QueryEditor = Backbone.View.extend({ + className: 'recline-query-editor', + template: ' \ +
        \ +
        \ + \ + \ +
        \ + \ + \ +
        \ + ', + + events: { + 'submit form': 'onFormSubmit', + 'click .action-pagination-update': 'onPaginationUpdate' + }, + + initialize: function() { + _.bindAll(this, 'render'); + this.el = $(this.el); + this.model.bind('change', this.render); + this.render(); + }, + onFormSubmit: function(e) { + e.preventDefault(); + var query = this.el.find('.text-query input').val(); + var newFrom = parseInt(this.el.find('input[name="from"]').val()); + var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom; + this.model.set({size: newSize, from: newFrom, q: query}); + }, + onPaginationUpdate: function(e) { + e.preventDefault(); + var $el = $(e.target); + var newFrom = 0; + if ($el.parent().hasClass('prev')) { + newFrom = this.model.get('from') - Math.max(0, this.model.get('size')); + } else { + newFrom = this.model.get('from') + this.model.get('size'); + } + this.model.set({from: newFrom}); + }, + render: function() { + var tmplData = this.model.toJSON(); + tmplData.to = this.model.get('from') + this.model.get('size'); + var templated = Mustache.render(this.template, tmplData); + this.el.html(templated); + } +}); + +})(jQuery, recline.View); + diff --git a/test/index.html b/test/index.html index 1b9e301a..6a328121 100644 --- a/test/index.html +++ b/test/index.html @@ -41,20 +41,22 @@ - + + + - + diff --git a/test/view.test.js b/test/view.multiview.test.js similarity index 89% rename from test/view.test.js rename to test/view.multiview.test.js index b00b7f1d..fc7ce53a 100644 --- a/test/view.test.js +++ b/test/view.multiview.test.js @@ -6,7 +6,7 @@ test('basic explorer functionality', function () { var $el = $('
        '); $('.fixtures .data-explorer-here').append($el); var dataset = Fixture.getDataset(); - var explorer = new recline.View.DataExplorer({ + var explorer = new recline.View.MultiView({ model: dataset, el: $el }); @@ -21,7 +21,7 @@ test('get State', function () { var dataset = Fixture.getDataset(); var url = 'xyz'; dataset.set({url: url}); - var explorer = new recline.View.DataExplorer({ + var explorer = new recline.View.MultiView({ model: dataset, el: $el }); @@ -41,7 +41,7 @@ test('initialize state', function () { var $el = $('
        '); $('.fixtures .data-explorer-here').append($el); var dataset = Fixture.getDataset(); - var explorer = new recline.View.DataExplorer({ + var explorer = new recline.View.MultiView({ model: dataset, el: $el, state: { @@ -74,11 +74,11 @@ test('initialize state', function () { test('restore (from serialized state)', function() { var dataset = Fixture.getDataset(); - var explorer = new recline.View.DataExplorer({ + var explorer = new recline.View.MultiView({ model: dataset, }); var state = explorer.state.toJSON(); - var explorerNew = recline.View.DataExplorer.restore(state); + var explorerNew = recline.View.MultiView.restore(state); var out = explorerNew.state.toJSON(); equal(out.backend, state.backend); }); From 03ab0da72df2e27fd2f98d781b58b11dd6061774 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 31 May 2012 21:49:06 +0100 Subject: [PATCH 3/8] [#124,refactor][s]: addendum to last commit (forgot to split out filtereditor and delete from multiview). --- app/index.html | 1 + src/view.multiview.js | 325 +------------------------------------ src/widget.filtereditor.js | 109 +++++++++++++ test/index.html | 1 + 4 files changed, 114 insertions(+), 322 deletions(-) create mode 100644 src/widget.filtereditor.js diff --git a/app/index.html b/app/index.html index 4b16c85a..d38b71b2 100644 --- a/app/index.html +++ b/app/index.html @@ -66,6 +66,7 @@ + diff --git a/src/view.multiview.js b/src/view.multiview.js index 78f74372..7f818a91 100644 --- a/src/view.multiview.js +++ b/src/view.multiview.js @@ -1,98 +1,5 @@ /*jshint multistr:true */ -// # Recline Views -// -// Recline Views are instances of Backbone Views and they act as 'WUI' (web -// user interface) component displaying some model object in the DOM. Like all -// Backbone views they have a pointer to a model (or a collection) and have an -// associated DOM-style element (usually this element will be bound into the -// page at some point). -// -// Views provided by core Recline are crudely divided into two types: -// -// * Dataset Views: a View intended for displaying a recline.Model.Dataset -// in some fashion. Examples are the Grid, Graph and Map views. -// * Widget Views: a widget used for displaying some specific (and -// smaller) aspect of a dataset or the application. Examples are -// QueryEditor and FilterEditor which both provide a way for editing (a -// part of) a `recline.Model.Query` associated to a Dataset. -// -// ## Dataset View -// -// These views are just Backbone views with a few additional conventions: -// -// 1. The model passed to the View should always be a recline.Model.Dataset instance -// 2. Views should generate their own root element rather than having it passed -// in. -// 3. Views should apply a css class named 'recline-{view-name-lower-cased} to -// the root element (and for all CSS for this view to be qualified using this -// CSS class) -// 4. Read-only mode: CSS for this view should respect/utilize -// recline-read-only class to trigger read-only behaviour (this class will -// usually be set on some parent element of the view's root element. -// 5. State: state (configuration) information for the view should be stored on -// an attribute named state that is an instance of a Backbone Model (or, more -// speficially, be an instance of `recline.Model.ObjectState`). In addition, -// a state attribute may be specified in the Hash passed to a View on -// iniitialization and this information should be used to set the initial -// state of the view. -// -// Example of state would be the set of fields being plotted in a graph -// view. -// -// More information about State can be found below. -// -// To summarize some of this, the initialize function for a Dataset View should -// look like: -// -//
        -//    initialize: {
        -//        model: {a recline.Model.Dataset instance}
        -//        // el: {do not specify - instead view should create}
        -//        state: {(optional) Object / Hash specifying initial state}
        -//        ...
        -//    }
        -// 
        -// -// Note: Dataset Views in core Recline have a common layout on disk as -// follows, where ViewName is the named of View class: -// -//
        -// src/view-{lower-case-ViewName}.js
        -// css/{lower-case-ViewName}.css
        -// test/view-{lower-case-ViewName}.js
        -// 
        -// -// ### State -// -// State information exists in order to support state serialization into the -// url or elsewhere and reloading of application from a stored state. -// -// State is available not only for individual views (as described above) but -// for the dataset (e.g. the current query). For an example of pulling together -// state from across multiple components see `recline.View.MultiView`. -// -// ### Flash Messages / Notifications -// -// To send 'flash messages' or notifications the convention is that views -// should fire an event named `recline:flash` with a payload that is a -// flash object with the following attributes (all optional): -// -// * message: message to show. -// * category: warning (default), success, error -// * persist: if true alert is persistent, o/w hidden after 3s (default=false) -// * loader: if true show a loading message -// -// Objects or views wishing to bind to flash messages may then subscribe to -// these events and take some action such as displaying them to the user. For -// an example of such behaviour see the MultiView view. -// -// ### Writing your own Views -// -// See the existing Views. -// -// ---- - // Standard JS module setup this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; @@ -292,16 +199,16 @@ my.MultiView = Backbone.View.extend({ _.each(this.pageViews, function(view, pageName) { $dataViewContainer.append(view.view.el); }); - var queryEditor = new my.QueryEditor({ + var queryEditor = new recline.View.QueryEditor({ model: this.model.queryState }); this.el.find('.query-editor-here').append(queryEditor.el); - var filterEditor = new my.FilterEditor({ + var filterEditor = new recline.View.FilterEditor({ model: this.model.queryState }); this.$filterEditor = filterEditor.el; this.el.find('.header').append(filterEditor.el); - var facetViewer = new my.FacetViewer({ + var facetViewer = new recline.View.FacetViewer({ model: this.model }); this.$facetViewer = facetViewer.el; @@ -465,231 +372,5 @@ my.MultiView.restore = function(state) { return explorer; } -my.QueryEditor = Backbone.View.extend({ - className: 'recline-query-editor', - template: ' \ -
        \ -
        \ - \ - \ -
        \ - \ - \ -
        \ - ', - - events: { - 'submit form': 'onFormSubmit', - 'click .action-pagination-update': 'onPaginationUpdate' - }, - - initialize: function() { - _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.bind('change', this.render); - this.render(); - }, - onFormSubmit: function(e) { - e.preventDefault(); - var query = this.el.find('.text-query input').val(); - var newFrom = parseInt(this.el.find('input[name="from"]').val()); - var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom; - this.model.set({size: newSize, from: newFrom, q: query}); - }, - onPaginationUpdate: function(e) { - e.preventDefault(); - var $el = $(e.target); - var newFrom = 0; - if ($el.parent().hasClass('prev')) { - newFrom = this.model.get('from') - Math.max(0, this.model.get('size')); - } else { - newFrom = this.model.get('from') + this.model.get('size'); - } - this.model.set({from: newFrom}); - }, - render: function() { - var tmplData = this.model.toJSON(); - tmplData.to = this.model.get('from') + this.model.get('size'); - var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); - } -}); - -my.FilterEditor = Backbone.View.extend({ - className: 'recline-filter-editor well', - template: ' \ - × \ -
        \ -
        \ -

        Filters

        \ -
        \ -
        \ -
        \ -
        \ -
        \ - {{#termFilters}} \ -
        \ - \ -
        \ -
        \ - \ - \ -
        \ -
        \ -
        \ - {{/termFilters}} \ -
        \ -
        \ -

        To add a filter use the column menu in the grid view.

        \ - \ -
        \ - \ -
        \ -
        \ - ', - events: { - 'click .js-hide': 'onHide', - 'click .js-remove-filter': 'onRemoveFilter', - 'submit form': 'onTermFiltersUpdate' - }, - initialize: function() { - this.el = $(this.el); - _.bindAll(this, 'render'); - this.model.bind('change', this.render); - this.model.bind('change:filters:new-blank', this.render); - this.render(); - }, - render: function() { - var tmplData = $.extend(true, {}, this.model.toJSON()); - // we will use idx in list as there id ... - tmplData.filters = _.map(tmplData.filters, function(filter, idx) { - filter.id = idx; - return filter; - }); - tmplData.termFilters = _.filter(tmplData.filters, function(filter) { - return filter.term !== undefined; - }); - tmplData.termFilters = _.map(tmplData.termFilters, function(filter) { - var fieldId = _.keys(filter.term)[0]; - return { - id: filter.id, - fieldId: fieldId, - label: fieldId, - value: filter.term[fieldId] - }; - }); - var out = Mustache.render(this.template, tmplData); - this.el.html(out); - // are there actually any facets to show? - if (this.model.get('filters').length > 0) { - this.el.show(); - } else { - this.el.hide(); - } - }, - onHide: function(e) { - e.preventDefault(); - this.el.hide(); - }, - onRemoveFilter: function(e) { - e.preventDefault(); - var $target = $(e.target); - var filterId = $target.closest('.filter').attr('data-filter-id'); - this.model.removeFilter(filterId); - }, - onTermFiltersUpdate: function(e) { - var self = this; - e.preventDefault(); - var filters = self.model.get('filters'); - var $form = $(e.target); - _.each($form.find('input'), function(input) { - var $input = $(input); - var filterIndex = parseInt($input.attr('data-filter-id')); - var value = $input.val(); - var fieldId = $input.attr('data-filter-field'); - filters[filterIndex].term[fieldId] = value; - }); - self.model.set({filters: filters}); - self.model.trigger('change'); - } -}); - -my.FacetViewer = Backbone.View.extend({ - className: 'recline-facet-viewer well', - template: ' \ - × \ -
        \ -
        \ -

        Facets

        \ -
        \ - {{#facets}} \ - \ - {{/facets}} \ -
        \ - ', - - events: { - 'click .js-hide': 'onHide', - 'click .js-facet-filter': 'onFacetFilter' - }, - initialize: function(model) { - _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.facets.bind('all', this.render); - this.model.fields.bind('all', this.render); - this.render(); - }, - render: function() { - var tmplData = { - facets: this.model.facets.toJSON(), - fields: this.model.fields.toJSON() - }; - tmplData.facets = _.map(tmplData.facets, function(facet) { - if (facet._type === 'date_histogram') { - facet.entries = _.map(facet.entries, function(entry) { - entry.term = new Date(entry.time).toDateString(); - return entry; - }); - } - return facet; - }); - var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); - // are there actually any facets to show? - if (this.model.facets.length > 0) { - this.el.show(); - } else { - this.el.hide(); - } - }, - onHide: function(e) { - e.preventDefault(); - this.el.hide(); - }, - onFacetFilter: function(e) { - var $target= $(e.target); - var fieldId = $target.closest('.facet-summary').attr('data-facet'); - var value = $target.attr('data-value'); - this.model.queryState.addTermFilter(fieldId, value); - } -}); - - })(jQuery, recline.View); diff --git a/src/widget.filtereditor.js b/src/widget.filtereditor.js new file mode 100644 index 00000000..1564e3ab --- /dev/null +++ b/src/widget.filtereditor.js @@ -0,0 +1,109 @@ +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + +my.FilterEditor = Backbone.View.extend({ + className: 'recline-filter-editor well', + template: ' \ + × \ +
        \ +
        \ +

        Filters

        \ +
        \ +
        \ +
        \ +
        \ +
        \ + {{#termFilters}} \ +
        \ + \ +
        \ +
        \ + \ + \ +
        \ +
        \ +
        \ + {{/termFilters}} \ +
        \ +
        \ +

        To add a filter use the column menu in the grid view.

        \ + \ +
        \ + \ +
        \ +
        \ + ', + events: { + 'click .js-hide': 'onHide', + 'click .js-remove-filter': 'onRemoveFilter', + 'submit form': 'onTermFiltersUpdate' + }, + initialize: function() { + this.el = $(this.el); + _.bindAll(this, 'render'); + this.model.bind('change', this.render); + this.model.bind('change:filters:new-blank', this.render); + this.render(); + }, + render: function() { + var tmplData = $.extend(true, {}, this.model.toJSON()); + // we will use idx in list as there id ... + tmplData.filters = _.map(tmplData.filters, function(filter, idx) { + filter.id = idx; + return filter; + }); + tmplData.termFilters = _.filter(tmplData.filters, function(filter) { + return filter.term !== undefined; + }); + tmplData.termFilters = _.map(tmplData.termFilters, function(filter) { + var fieldId = _.keys(filter.term)[0]; + return { + id: filter.id, + fieldId: fieldId, + label: fieldId, + value: filter.term[fieldId] + }; + }); + var out = Mustache.render(this.template, tmplData); + this.el.html(out); + // are there actually any facets to show? + if (this.model.get('filters').length > 0) { + this.el.show(); + } else { + this.el.hide(); + } + }, + onHide: function(e) { + e.preventDefault(); + this.el.hide(); + }, + onRemoveFilter: function(e) { + e.preventDefault(); + var $target = $(e.target); + var filterId = $target.closest('.filter').attr('data-filter-id'); + this.model.removeFilter(filterId); + }, + onTermFiltersUpdate: function(e) { + var self = this; + e.preventDefault(); + var filters = self.model.get('filters'); + var $form = $(e.target); + _.each($form.find('input'), function(input) { + var $input = $(input); + var filterIndex = parseInt($input.attr('data-filter-id')); + var value = $input.val(); + var fieldId = $input.attr('data-filter-field'); + filters[filterIndex].term[fieldId] = value; + }); + self.model.set({filters: filters}); + self.model.trigger('change'); + } +}); + + +})(jQuery, recline.View); + diff --git a/test/index.html b/test/index.html index 6a328121..b38518d8 100644 --- a/test/index.html +++ b/test/index.html @@ -48,6 +48,7 @@ + From 4bfcb3c02742b5a0a4649512320ac0e071ae374e Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Thu, 31 May 2012 21:54:13 +0100 Subject: [PATCH 4/8] [#124,refactor][s]: merge util.js into view.multiview.js as only utilities are query string parsing utilities and that is where they are used. --- README.md | 2 ++ app/index.html | 1 - app/js/app.js | 4 +-- src/util.js | 79 ------------------------------------------- src/view.multiview.js | 74 +++++++++++++++++++++++++++++++++++++++- test/index.html | 1 - test/util.test.js | 4 +-- 7 files changed, 79 insertions(+), 86 deletions(-) delete mode 100644 src/util.js diff --git a/README.md b/README.md index be7e8ce6..fc65ceb0 100755 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ Possible breaking changes: * State only stores backend (name) and dataset url (in url field) rather than entire dataset object * Backends heavily reorganized +* Rename Document -> Record +* Rename DataExplorer view to MultiView ### v0.4 - April 26th 2012 diff --git a/app/index.html b/app/index.html index d38b71b2..39e65a98 100644 --- a/app/index.html +++ b/app/index.html @@ -49,7 +49,6 @@ - diff --git a/app/js/app.js b/app/js/app.js index 7de2ee58..fb8d375e 100755 --- a/app/js/app.js +++ b/app/js/app.js @@ -21,7 +21,7 @@ var ExplorerApp = Backbone.View.extend({ this.router.route(/explorer/, 'explorer', this.viewExplorer); Backbone.history.start(); - var state = recline.Util.parseQueryString(decodeURIComponent(window.location.search)); + var state = recline.View.parseQueryString(decodeURIComponent(window.location.search)); if (state) { _.each(state, function(value, key) { try { @@ -145,7 +145,7 @@ var ExplorerApp = Backbone.View.extend({ }, makePermaLink: function(state) { - var qs = recline.Util.composeQueryString(state.toJSON()); + var qs = recline.View.composeQueryString(state.toJSON()); return window.location.origin + window.location.pathname + qs; }, diff --git a/src/util.js b/src/util.js deleted file mode 100644 index dbba7b0e..00000000 --- a/src/util.js +++ /dev/null @@ -1,79 +0,0 @@ -/*jshint multistr:true */ - -this.recline = this.recline || {}; -this.recline.Util = this.recline.Util || {}; - -(function(my) { -// ## 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 {}; - } else { - return { - path: parsed[1], - query: parsed[2] || '' - }; - } -}; - -// Parse a URL query string (?xyz=abc...) into a dictionary. -my.parseQueryString = function(q) { - if (!q) { - return {}; - } - var urlParams = {}, - e, d = function (s) { - return unescape(s.replace(/\+/g, " ")); - }, - r = /([^&=]+)=?([^&]*)/g; - - 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]); - } - return urlParams; -}; - -// 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) { - var queryString = '?'; - var items = []; - $.each(queryParams, function(key, value) { - if (typeof(value) === 'object') { - value = JSON.stringify(value); - } - items.push(key + '=' + encodeURIComponent(value)); - }); - queryString += items.join('&'); - return queryString; -}; - -my.getNewHashForQueryString = function(queryParams) { - var queryPart = my.composeQueryString(queryParams); - if (window.location.hash) { - // slice(1) to remove # at start - return window.location.hash.split('?')[0].slice(1) + queryPart; - } else { - return queryPart; - } -}; - -my.setHashQueryString = function(queryParams) { - window.location.hash = my.getNewHashForQueryString(queryParams); -}; -})(this.recline.Util); - diff --git a/src/view.multiview.js b/src/view.multiview.js index 7f818a91..56eb8b69 100644 --- a/src/view.multiview.js +++ b/src/view.multiview.js @@ -258,7 +258,7 @@ my.MultiView = Backbone.View.extend({ _setupState: function(initialState) { var self = this; // get data from the query string / hash url plus some defaults - var qs = recline.Util.parseHashQueryString(); + var qs = my.parseHashQueryString(); var query = qs.reclineQuery; query = query ? JSON.parse(query) : self.model.queryState.toJSON(); // backwards compatability (now named view-graph but was named graph) @@ -372,5 +372,77 @@ my.MultiView.restore = function(state) { return explorer; } + +// ## 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 {}; + } else { + return { + path: parsed[1], + query: parsed[2] || '' + }; + } +}; + +// Parse a URL query string (?xyz=abc...) into a dictionary. +my.parseQueryString = function(q) { + if (!q) { + return {}; + } + var urlParams = {}, + e, d = function (s) { + return unescape(s.replace(/\+/g, " ")); + }, + r = /([^&=]+)=?([^&]*)/g; + + 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]); + } + return urlParams; +}; + +// 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) { + var queryString = '?'; + var items = []; + $.each(queryParams, function(key, value) { + if (typeof(value) === 'object') { + value = JSON.stringify(value); + } + items.push(key + '=' + encodeURIComponent(value)); + }); + queryString += items.join('&'); + return queryString; +}; + +my.getNewHashForQueryString = function(queryParams) { + var queryPart = my.composeQueryString(queryParams); + if (window.location.hash) { + // slice(1) to remove # at start + return window.location.hash.split('?')[0].slice(1) + queryPart; + } else { + return queryPart; + } +}; + +my.setHashQueryString = function(queryParams) { + window.location.hash = my.getNewHashForQueryString(queryParams); +}; + })(jQuery, recline.View); diff --git a/test/index.html b/test/index.html index b38518d8..cacc7319 100644 --- a/test/index.html +++ b/test/index.html @@ -27,7 +27,6 @@ - diff --git a/test/util.test.js b/test/util.test.js index c4d7f930..6490d6dd 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -2,7 +2,7 @@ module("Util"); test('parseHashUrl', function () { - var out = recline.Util.parseHashUrl('graph?x=y'); + var out = recline.View.parseHashUrl('graph?x=y'); equal(out.path, 'graph'); equal(out.query, '?x=y'); var out = recline.Util.parseHashUrl('graph'); @@ -15,7 +15,7 @@ test('composeQueryString', function () { x: 'y', a: 'b' }; - var out = recline.Util.composeQueryString(params); + var out = recline.View.composeQueryString(params); equal(out, '?x=y&a=b'); }); From 40cc3550656db130ffad3ab916ec7812724b3c07 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 1 Jun 2012 14:38:24 +0100 Subject: [PATCH 5/8] [#124,css][xs]: rename data-explorer.css to multiview.css. --- _includes/recline-deps.html | 2 +- app/index.html | 2 +- css/{data-explorer.css => multiview.css} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename css/{data-explorer.css => multiview.css} (100%) diff --git a/_includes/recline-deps.html b/_includes/recline-deps.html index ad77703a..d5066d74 100644 --- a/_includes/recline-deps.html +++ b/_includes/recline-deps.html @@ -5,10 +5,10 @@ - + diff --git a/app/index.html b/app/index.html index 39e65a98..14b340a6 100644 --- a/app/index.html +++ b/app/index.html @@ -19,11 +19,11 @@ - + diff --git a/css/data-explorer.css b/css/multiview.css similarity index 100% rename from css/data-explorer.css rename to css/multiview.css From b63f4daad8af122ce637600f187509813bc4d698 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 1 Jun 2012 14:40:55 +0100 Subject: [PATCH 6/8] [#124,test][xs]: missing change to test - should have been in 4bfcb3c02742b5a0a4649512320ac0e071ae374e. --- test/util.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/util.test.js b/test/util.test.js index 6490d6dd..e6711c0d 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -5,7 +5,7 @@ test('parseHashUrl', function () { var out = recline.View.parseHashUrl('graph?x=y'); equal(out.path, 'graph'); equal(out.query, '?x=y'); - var out = recline.Util.parseHashUrl('graph'); + var out = recline.View.parseHashUrl('graph'); equal(out.path, 'graph'); equal(out.query, ''); }); From 86fbe6d191ecbe04f800783888d49d0d0affe030 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 1 Jun 2012 17:52:12 +0100 Subject: [PATCH 7/8] [#140,refactor,widget/pager][s]: split out pager from query editor into separate widget and display in different place in multiview - fixes #140. --- app/index.html | 1 + css/multiview.css | 48 ++++++++++++++++++++------------- src/view.multiview.js | 4 +++ src/widget.pager.js | 57 +++++++++++++++++++++++++++++++++++++++ src/widget.queryeditor.js | 25 ++--------------- test/index.html | 1 + 6 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 src/widget.pager.js diff --git a/app/index.html b/app/index.html index 14b340a6..6fd4dc9a 100644 --- a/app/index.html +++ b/app/index.html @@ -64,6 +64,7 @@ + diff --git a/css/multiview.css b/css/multiview.css index d1a6a94f..dee4edd5 100644 --- a/css/multiview.css +++ b/css/multiview.css @@ -27,7 +27,7 @@ .header .recline-results-info { line-height: 28px; margin-left: 20px; - display: inline; + float: left; } /********************************************************** @@ -39,42 +39,54 @@ height: 30px; } -.header .recline-query-editor .input-prepend { +.header .input-prepend { margin-bottom: auto; } -.recline-query-editor .add-on { +.header .add-on { float: left; } /* needed for Chrome but not FF */ -.header .recline-query-editor .add-on { +.header .add-on { margin-left: -27px; } /* needed for FF but not chrome */ -.header .recline-query-editor .input-prepend { +.header .input-prepend { vertical-align: top; } -.header .recline-query-editor .pagination input { - width: 30px; - height: 18px; - padding: 2px 4px; - margin-top: -4px; -} - -.header .recline-query-editor .pagination a { - line-height: 26px; - padding: 0 6px; -} - .header .recline-query-editor form button { vertical-align: top; } /********************************************************** - * Query Editor + * Pager + *********************************************************/ + +.header .recline-pager { + float: left; + margin: auto; + display: block; + margin-left: 20px; +} + +.header .recline-pager .pagination input { + width: 30px; + height: 18px; + padding: 2px 4px; + margin: 0; + margin-top: -4px; +} + +.header .recline-pager .pagination a { + line-height: 26px; + padding: 0 6px; +} + +/********************************************************** + * Filter Editor *********************************************************/ .recline-filter-editor .filter-term .input-append a { diff --git a/src/view.multiview.js b/src/view.multiview.js index 56eb8b69..ebec0ab9 100644 --- a/src/view.multiview.js +++ b/src/view.multiview.js @@ -199,6 +199,10 @@ my.MultiView = Backbone.View.extend({ _.each(this.pageViews, function(view, pageName) { $dataViewContainer.append(view.view.el); }); + var pager = new recline.View.Pager({ + model: this.model.queryState + }); + this.el.find('.recline-results-info').after(pager.el); var queryEditor = new recline.View.QueryEditor({ model: this.model.queryState }); diff --git a/src/widget.pager.js b/src/widget.pager.js new file mode 100644 index 00000000..10fa4196 --- /dev/null +++ b/src/widget.pager.js @@ -0,0 +1,57 @@ +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + +my.Pager = Backbone.View.extend({ + className: 'recline-pager', + template: ' \ + \ + ', + + events: { + 'click .action-pagination-update': 'onPaginationUpdate', + 'change input': 'onFormSubmit' + }, + + initialize: function() { + _.bindAll(this, 'render'); + this.el = $(this.el); + this.model.bind('change', this.render); + this.render(); + }, + 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; + this.model.set({size: newSize, from: newFrom}); + }, + onPaginationUpdate: function(e) { + e.preventDefault(); + var $el = $(e.target); + var newFrom = 0; + if ($el.parent().hasClass('prev')) { + newFrom = this.model.get('from') - Math.max(0, this.model.get('size')); + } else { + newFrom = this.model.get('from') + this.model.get('size'); + } + this.model.set({from: newFrom}); + }, + render: function() { + var tmplData = this.model.toJSON(); + tmplData.to = this.model.get('from') + this.model.get('size'); + var templated = Mustache.render(this.template, tmplData); + this.el.html(templated); + } +}); + +})(jQuery, recline.View); + diff --git a/src/widget.queryeditor.js b/src/widget.queryeditor.js index 964ff97d..e576e279 100644 --- a/src/widget.queryeditor.js +++ b/src/widget.queryeditor.js @@ -13,20 +13,12 @@ my.QueryEditor = Backbone.View.extend({ \ \
        \ - \ \ \ ', events: { - 'submit form': 'onFormSubmit', - 'click .action-pagination-update': 'onPaginationUpdate' + 'submit form': 'onFormSubmit' }, initialize: function() { @@ -38,20 +30,7 @@ my.QueryEditor = Backbone.View.extend({ onFormSubmit: function(e) { e.preventDefault(); var query = this.el.find('.text-query input').val(); - var newFrom = parseInt(this.el.find('input[name="from"]').val()); - var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom; - this.model.set({size: newSize, from: newFrom, q: query}); - }, - onPaginationUpdate: function(e) { - e.preventDefault(); - var $el = $(e.target); - var newFrom = 0; - if ($el.parent().hasClass('prev')) { - newFrom = this.model.get('from') - Math.max(0, this.model.get('size')); - } else { - newFrom = this.model.get('from') + this.model.get('size'); - } - this.model.set({from: newFrom}); + this.model.set({q: query}); }, render: function() { var tmplData = this.model.toJSON(); diff --git a/test/index.html b/test/index.html index cacc7319..84d0231e 100644 --- a/test/index.html +++ b/test/index.html @@ -46,6 +46,7 @@ + From 170f10268caad7ab5128009738f0d73beb8d0d6b Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 2 Jun 2012 19:02:55 +0100 Subject: [PATCH 8/8] [#141,view/map][s]: support for location objects with form {lon: ..., lat: ...}. --- src/view-map.js | 14 ++++++++++---- test/view-map.test.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/view-map.js b/src/view-map.js index f0a75ae8..79b19492 100644 --- a/src/view-map.js +++ b/src/view-map.js @@ -347,13 +347,19 @@ my.Map = Backbone.View.extend({ if (typeof(value) === 'string'){ // We *may* have a GeoJSON string representation try { - return $.parseJSON(value); + value = $.parseJSON(value); } catch(e) { } - } else { - // We assume that the contents of the field are a valid GeoJSON object - return value; } + if (value && value.lat) { + // not yet geojson so convert + value = { + "type": "Point", + "coordinates": [value.lon || value.lng, value.lat] + }; + } + // We now assume that contents of the field are a valid GeoJSON object + return value; } else if (this.state.get('lonField') && this.state.get('latField')){ // We'll create a GeoJSON like point object from the two lat/lon fields var lon = doc.get(this.state.get('lonField')); diff --git a/test/view-map.test.js b/test/view-map.test.js index 3ad5a4d2..61e4d5a3 100644 --- a/test/view-map.test.js +++ b/test/view-map.test.js @@ -90,6 +90,23 @@ test('GeoJSON geom field', function () { view.remove(); }); +test('geom field non-GeoJSON', function () { + var data = [{ + location: { lon: 47, lat: 53}, + title: 'abc' + }]; + var dataset = recline.Backend.Memory.createDataset(data); + var view = new recline.View.Map({ + model: dataset + }); + + //Fire query, otherwise the map won't be initialized + dataset.query(); + + // Check that all features were created + equal(_getFeaturesCount(view.features), 1); +}); + test('Popup', function () { var dataset = GeoJSONFixture.getDataset(); var view = new recline.View.Map({