From b3e68c152df78df7667741794055a5f652cef04f Mon Sep 17 00:00:00 2001 From: Daniel Beilinson Date: Tue, 23 Oct 2012 23:06:52 +0400 Subject: [PATCH 1/8] couchDB broken sort and filter fixed --- src/backend.couchdb.js | 83 +++++++++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 13 deletions(-) diff --git a/src/backend.couchdb.js b/src/backend.couchdb.js index 1691f756..4d338b48 100755 --- a/src/backend.couchdb.js +++ b/src/backend.couchdb.js @@ -317,12 +317,15 @@ my.query = function(queryObj, dataset) { query_result.hits = _applyFreeTextQuery(query_result.hits, queryObj); // not complete sorting! _.each(queryObj.sort, function(sortObj) { - var fieldName = _.keys(sortObj)[0]; - query_result.hits = _.sortBy(query_result.hits, function(doc) { - var _out = doc[fieldName]; - return (sortObj[fieldName].order == 'asc') ? _out : -1*_out; + var fieldName = sortObj.field; + query_result.hits = _.sortBy(query_result.hits, function(doc) { + var _out = doc[fieldName]; + return _out; + }); + if (sortObj.order == 'desc') { + query_result.hits.reverse(); + } }); - }); query_result.total = query_result.hits.length; query_result.facets = _computeFacets(query_result.hits, queryObj); query_result.hits = query_result.hits.slice(cdb_q.skip, cdb_q.skip + cdb_q.limit+1); @@ -335,13 +338,67 @@ my.query = function(queryObj, dataset) { // in place filtering _applyFilters = function(results, queryObj) { - _.each(queryObj.filters, function(filter) { - results = _.filter(results, function(doc) { - var fieldId = _.keys(filter.term)[0]; - return (doc[fieldId] == filter.term[fieldId]); - }); - }); - return results; + var filters = queryObj.filters; + // register filters + var filterFunctions = { + term : term, + range : range, + geo_distance : geo_distance + }; + var dataParsers = { + integer: function (e) { return parseFloat(e, 10); }, + 'float': function (e) { return parseFloat(e, 10); }, + string : function (e) { return e.toString() }, + date : function (e) { return new Date(e).valueOf() }, + datetime : function (e) { return new Date(e).valueOf() } + }; + + function getDataParser(filter) { + //sample = results[0][filter.field]); + var fieldType = 'string'; + return dataParsers[fieldType]; + } + + // filter records + return _.filter(results, function (record) { + //alert(record); + var passes = _.map(filters, function (filter) { + return filterFunctions[filter.type](record, filter); + }); + + // return only these records that pass all filters + //alert(passes); + return _.all(passes, _.identity); + }); + + // filters definitions + function term(record, filter) { + var parse = getDataParser(filter); + var value = parse(record[filter.field]); + var term = parse(filter.term); + + return (value === term); + } + + function range(record, filter) { + var startnull = (filter.start == null || filter.start === ''); + var stopnull = (filter.stop == null || filter.stop === ''); + var parse = getDataParser(filter); + var value = parse(record[filter.field]); + var start = parse(filter.start); + var stop = parse(filter.stop); + + // if at least one end of range is set do not allow '' to get through + // note that for strings '' <= {any-character} e.g. '' <= 'a' + if ((!startnull || !stopnull) && value === '') { + return false; + } + return ((startnull || value >= start) && (stopnull || value <= stop)); + } + + function geo_distance() { + // TODO code here + } }; // we OR across fields but AND across terms in query string @@ -498,4 +555,4 @@ _deleteDocument = function (del_doc, dataset) { return dfd.promise(); } }; -}(jQuery, this.recline.Backend.CouchDB)); +}(jQuery, this.recline.Backend.CouchDB)); \ No newline at end of file From 7e471407a3b0963aebcd4f44520edbb100906f7e Mon Sep 17 00:00:00 2001 From: Daniel Beilinson Date: Wed, 24 Oct 2012 23:13:28 +0400 Subject: [PATCH 2/8] _createDocument fixed, randomId function added --- src/backend.couchdb.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/backend.couchdb.js b/src/backend.couchdb.js index 4d338b48..570bfbf4 100755 --- a/src/backend.couchdb.js +++ b/src/backend.couchdb.js @@ -361,13 +361,11 @@ _applyFilters = function(results, queryObj) { // filter records return _.filter(results, function (record) { - //alert(record); var passes = _.map(filters, function (filter) { return filterFunctions[filter.type](record, filter); }); // return only these records that pass all filters - //alert(passes); return _.all(passes, _.identity); }); @@ -463,6 +461,18 @@ _computeFacets = function(records, queryObj) { }); return facetResults; }; + +//Define random Id for new records without _id +function randomId(length, chars) { + var mask = ''; + if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; + if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (chars.indexOf('#') > -1) mask += '0123456789'; + if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; + var result = ''; + for (var i = length; i > 0; --i) result += mask[Math.round(Math.random() * (mask.length - 1))]; + return result; +} _createDocument = function (new_doc, dataset) { var dfd = $.Deferred(); @@ -471,25 +481,17 @@ _createDocument = function (new_doc, dataset) { var _id = new_doc['id']; var cdb = new my.CouchDBWrapper(db_url, view_url); - delete new_doc['id']; + delete new_doc['id']; - if (view_url.search('_all_docs') !== -1) { - jqxhr = cdb.get(_id); + if (dataset.record_create) + new_doc = dataset.record_create(new_doc); + if (_id !== 1) { + new_doc['_id'] = _id; } else { - _id = new_doc['_id'].split('__')[0]; - jqxhr = cdb.get(_id); + new_doc['_id'] = randomId(16, '#a'); } - - jqxhr.done(function(old_doc){ - if (dataset.record_create) - new_doc = dataset.record_create(new_doc, old_doc); - new_doc = _.extend(old_doc, new_doc); - new_doc['_id'] = _id; - dfd.resolve(cdb.upsert(new_doc)); - }).fail(function(args){ - dfd.reject(args); - }); + dfd.resolve(cdb.upsert(new_doc)); return dfd.promise(); }; From 7a12003a35a5099c3c92b2f8c9a06826d22ae696 Mon Sep 17 00:00:00 2001 From: Daniel Beilinson Date: Mon, 29 Oct 2012 01:43:52 +0400 Subject: [PATCH 3/8] undfinded _id for couchDB, 33 symbols ID generator --- src/backend.couchdb.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend.couchdb.js b/src/backend.couchdb.js index 570bfbf4..0ef3f625 100755 --- a/src/backend.couchdb.js +++ b/src/backend.couchdb.js @@ -485,11 +485,11 @@ _createDocument = function (new_doc, dataset) { if (dataset.record_create) new_doc = dataset.record_create(new_doc); - if (_id !== 1) { + if (_id !== 1 && _id !== undefined) { new_doc['_id'] = _id; } else { - new_doc['_id'] = randomId(16, '#a'); + new_doc['_id'] = randomId(32, '#a'); } dfd.resolve(cdb.upsert(new_doc)); From c7ff853acbac4e1af165e37c67ceade96c1109d1 Mon Sep 17 00:00:00 2001 From: Frederick Ros Date: Mon, 22 Oct 2012 19:07:57 +0200 Subject: [PATCH 4/8] Fixed typo in comments --- src/view.slickgrid.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/view.slickgrid.js b/src/view.slickgrid.js index c751c83f..bc98f09a 100644 --- a/src/view.slickgrid.js +++ b/src/view.slickgrid.js @@ -24,8 +24,8 @@ this.recline.View = this.recline.View || {}; // state: { // gridOptions: {editable: true}, // columnsEditor: [ -// {column: 'date', editor: Slick.Editor.Date }, -// {column: 'title', editor: Slick.Editor.Text} +// {column: 'date', editor: Slick.Editors.Date }, +// {column: 'title', editor: Slick.Editors.Text} // ] // } // }); From 24ee9fa0196cbd3f9e0f273df1eab1cf4562c4b7 Mon Sep 17 00:00:00 2001 From: Frederick Ros Date: Sun, 21 Oct 2012 01:56:02 +0200 Subject: [PATCH 5/8] Update the grid when a record is changed Fixes #255 --- src/view.slickgrid.js | 34 +++++++++++++++++++++++++++++----- test/view.slickgrid.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/view.slickgrid.js b/src/view.slickgrid.js index bc98f09a..d9f61deb 100644 --- a/src/view.slickgrid.js +++ b/src/view.slickgrid.js @@ -39,6 +39,7 @@ my.SlickGrid = Backbone.View.extend({ this.model.records.bind('add', this.render); this.model.records.bind('reset', this.render); this.model.records.bind('remove', this.render); + this.model.records.bind('change', this.onRecordChanged, this) var state = _.extend({ hiddenColumns: [], @@ -58,6 +59,19 @@ my.SlickGrid = Backbone.View.extend({ events: { }, + onRecordChanged: function(record) { + // Ignore if the grid is not yet drawn + if (!this.grid) { + return; + } + + // Let's find the row corresponding to the index + var row_index = this.grid.getData().getModelRow( record ); + this.grid.invalidateRow(row_index); + this.grid.getData().updateItem(record, row_index); + this.grid.render(); + }, + render: function() { var self = this; @@ -129,6 +143,15 @@ my.SlickGrid = Backbone.View.extend({ } columns = columns.concat(tempHiddenColumns); + // Transform a model object into a row + function toRow(m) { + var row = {}; + self.model.fields.each(function(field){ + row[field.id] = m.getFieldValueUnrendered(field); + }); + return row; + } + function RowSet() { var models = []; var rows = []; @@ -142,16 +165,17 @@ my.SlickGrid = Backbone.View.extend({ this.getItem = function(index) { return rows[index];} this.getItemMetadata= function(index) { return {};} this.getModel= function(index) { return models[index]; } + this.getModelRow = function(m) { return models.indexOf(m);} + this.updateItem = function(m,i) { + rows[i] = toRow(m); + models[i] = m + }; }; var data = new RowSet(); this.model.records.each(function(doc){ - var row = {}; - self.model.fields.each(function(field){ - row[field.id] = doc.getFieldValueUnrendered(field); - }); - data.push(doc, row); + data.push(doc, toRow(doc)); }); this.grid = new Slick.Grid(this.el, data, visibleColumns, options); diff --git a/test/view.slickgrid.test.js b/test/view.slickgrid.test.js index f3cf05f3..0ff639b1 100644 --- a/test/view.slickgrid.test.js +++ b/test/view.slickgrid.test.js @@ -101,6 +101,31 @@ test('editable', function () { }, e, view.grid); }); +test('update', function() { + var dataset = Fixture.getDataset(); + var view = new recline.View.SlickGrid({ + model: dataset, + state: { + hiddenColumns:['x','lat','title'], + columnsOrder:['lon','id','z','date', 'y', 'country'], + columnsWidth:[ + {column:'id',width: 250} + ], + gridOptions: {editable: true}, + columnsEditor: [{column: 'country', editor: Slick.Editors.Text}] + } + }); + + $('.fixtures .test-datatable').append(view.el); + view.render(); + view.grid.init(); + + var zbefore = view.grid.getData().getItem(1)['z']; + // Change the model at row 1 + dataset.records.at(1).set('z', zbefore + 1); + equal( zbefore + 1, view.grid.getData().getItem(1)['z']); +}); + test('renderers', function () { var dataset = Fixture.getDataset(); From 4e8a8eb340c6d0d4b40934e2ad184f48fd479052 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 2 Nov 2012 09:36:25 +0000 Subject: [PATCH 6/8] [#183,demos/search][xs]: rename js file. --- demos/search/{app.js => demo.search.app.js} | 0 demos/search/index.html | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename demos/search/{app.js => demo.search.app.js} (100%) diff --git a/demos/search/app.js b/demos/search/demo.search.app.js similarity index 100% rename from demos/search/app.js rename to demos/search/demo.search.app.js diff --git a/demos/search/index.html b/demos/search/index.html index a26646b6..ae54b243 100644 --- a/demos/search/index.html +++ b/demos/search/index.html @@ -85,7 +85,7 @@ ul.facet-items {
-

This demo shows how Recline can be used to build a search app. It includes faceting as well as search. You can find the source javascript here – please feel free to reuse!

+

This demo shows how Recline can be used to build a search app. It includes faceting as well as search. You can find the source javascript here – please feel free to reuse!

The default setup uses local example data but you can also connect directly to any other backend supported by Recline, for example SOLR, ElasticSearch or even a google docs spreadsheet. As an example: here's an example running against the SOLR-style OpenSpending API and here's an example running against a GDocs spreadsheet (Oil spills in the Niger Delta).

@@ -95,5 +95,5 @@ ul.facet-items {
- + From da1da609427b0dee3d9493032760724b093cd1ff Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 2 Nov 2012 09:56:09 +0000 Subject: [PATCH 7/8] [docs][xs]: latest version of docco.css. --- docs/src/docco.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/src/docco.css b/docs/src/docco.css index 5aa0a8d7..04cc7ecb 100644 --- a/docs/src/docco.css +++ b/docs/src/docco.css @@ -21,6 +21,12 @@ h1, h2, h3, h4, h5, h6 { h1 { margin-top: 40px; } +hr { + border: 0 none; + border-top: 1px solid #e5e5ee; + height: 1px; + margin: 20px 0; +} #container { position: relative; } @@ -115,7 +121,7 @@ table td { } pre, tt, code { font-size: 12px; line-height: 18px; - font-family: Monaco, Consolas, "Lucida Console", monospace; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; margin: 0; padding: 0; } From 3362083db6d151ae19c1522e51ba2071fbb531b1 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Fri, 2 Nov 2012 09:58:42 +0000 Subject: [PATCH 8/8] [#183,demos/search][s]: improve commenting in search demo js and produced docco'd version. --- demos/search/demo.search.app.js | 72 +++++++--- demos/search/index.html | 2 +- docs/src/demo.search.app.html | 236 ++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 17 deletions(-) create mode 100644 docs/src/demo.search.app.html diff --git a/demos/search/demo.search.app.js b/demos/search/demo.search.app.js index df2f9bfc..72b5937a 100644 --- a/demos/search/demo.search.app.js +++ b/demos/search/demo.search.app.js @@ -1,25 +1,43 @@ +// (c) Open Knowledge Foundation 2012. Dedicated to the public domain. Please +// use and reuse freely - you don't even need to credit (though a link back to +// ReclineJS.com is always appreciated)! + + +// ## Our main loop - on document ready jQuery(function($) { var $el = $('.search-here'); + // ### Overview + // + // We have a slightly more complex setup than is needed to allow for using + // this demo with different backends + // + // There are 2 alternatives: more complex and a simpler one + // + // If you just want to see how this work skip to the simple case ... + + // ### 1. More complex - use data from a backend configured in query string + // Check for config from url query string - // (this allows us to point to specific data sources backends) var config = recline.View.parseQueryString(decodeURIComponent(window.location.search)); if (config.backend) { + // If we had it hand off to our more complex example setup setupMoreComplexExample(config); return; } - // the simple example case + // ### 2. The simple example case + // + // We will just set up from some example local data (at the bottom of thile file) - // Create our Recline Dataset - // We'll just use some sample local data (see end of this file) + // #### Create our Recline Dataset from sample local data var dataset = new recline.Model.Dataset({ records: sampleData }); - // Set up the search View - - // We give it a custom template for rendering the example records + // #### Custom template + // + // Create a custom template for rendering the records var template = ' \
\

\ @@ -29,6 +47,8 @@ jQuery(function($) {

${{price}}

\

\ '; + + // #### Set up the search View (using custom template) var searchView = new SearchView({ el: $el, model: dataset, @@ -36,40 +56,48 @@ jQuery(function($) { }); searchView.render(); - // Optional - we configure the initial query a bit and set up facets + // #### Optional - we configure the initial query a bit and set up facets dataset.queryState.set({ size: 10 }, {silent: true} ); dataset.queryState.addFacet('Author'); - // Now do the first query - // After this point the Search View will take over handling queries + + // #### Finally - now do the first query + // + // After this point the Search View will take over handling queries! dataset.query(); }); -// Simple Search View +// ## Simple Search View // -// Pulls together various Recline UI components and the central Dataset and Query (state) object -// -// Plus support for customization e.g. of template for list of results +// This is a simple bespoke Backbone view for the Search. It Pulls together +// various Recline UI components and the central Dataset and Query (state) +// object // +// It also provides simple support for customization e.g. of template for list of results // // var view = new SearchView({ // el: $('some-element'), // model: dataset +// // EITHER a mustache template (passed a JSON version of recline.Model.Record +// // OR a function which receives a record in JSON form and returns html // template: mustache-template-or-function // }); var SearchView = Backbone.View.extend({ initialize: function(options) { this.el = $(this.el); _.bindAll(this, 'render'); - this.recordTemplate = options.template || this.defaultTemplate; + this.recordTemplate = options.template; + // Every time we do a search the recline.Dataset.records Backbone + // collection will get reset. We want to re-render each time! this.model.records.bind('reset', this.render); this.templateResults = options.template; }, + // overall template for this view template: ' \
\
\ @@ -84,6 +112,7 @@ var SearchView = Backbone.View.extend({
\ ', + // render the view render: function() { var results = ''; if (_.isFunction(this.templateResults)) { @@ -100,8 +129,13 @@ var SearchView = Backbone.View.extend({ }); this.el.html(html); + // Set the total records found info this.el.find('.total span').text(this.model.recordCount); + // ### Now setup all the extra mini-widgets + // + // Facets, Pager, QueryEditor etc + var view = new recline.View.FacetViewer({ model: this.model }); @@ -121,8 +155,14 @@ var SearchView = Backbone.View.extend({ }); // -------------------------------------------------------- -// Stuff very specific to this demo +// ## Custom code very specific to this demo +// e.g. to provide custom templates for the google doc and openspending examples + + +// ### Handle case where we get data from a specific backend +// +// Includes providing custom templates function setupMoreComplexExample(config) { var $el = $('.search-here'); var dataset = new recline.Model.Dataset(config); diff --git a/demos/search/index.html b/demos/search/index.html index ae54b243..e19ada5b 100644 --- a/demos/search/index.html +++ b/demos/search/index.html @@ -85,7 +85,7 @@ ul.facet-items {
-

This demo shows how Recline can be used to build a search app. It includes faceting as well as search. You can find the source javascript here – please feel free to reuse!

+

This demo shows how Recline can be used to build a search app. It includes faceting as well as search. You can find the source javascript here (plus prettified version of source for readability) – please feel free to reuse!

The default setup uses local example data but you can also connect directly to any other backend supported by Recline, for example SOLR, ElasticSearch or even a google docs spreadsheet. As an example: here's an example running against the SOLR-style OpenSpending API and here's an example running against a GDocs spreadsheet (Oil spills in the Niger Delta).

diff --git a/docs/src/demo.search.app.html b/docs/src/demo.search.app.html new file mode 100644 index 00000000..e97a180f --- /dev/null +++ b/docs/src/demo.search.app.html @@ -0,0 +1,236 @@ + demo.search.app.js

demo.search.app.js

(c) Open Knowledge Foundation 2012. Dedicated to the public domain. Please +use and reuse freely - you don't even need to credit (though a link back to +ReclineJS.com is always appreciated)!

Our main loop - on document ready

jQuery(function($) {
+  var $el = $('.search-here');

Overview

+ +

We have a slightly more complex setup than is needed to allow for using +this demo with different backends

+ +

There are 2 alternatives: more complex and a simpler one

+ +

If you just want to see how this work skip to the simple case ...

1. More complex - use data from a backend configured in query string

Check for config from url query string

  var config = recline.View.parseQueryString(decodeURIComponent(window.location.search));
+  if (config.backend) {

If we had it hand off to our more complex example setup

    setupMoreComplexExample(config);
+    return;
+  }

2. The simple example case

+ +

We will just set up from some example local data (at the bottom of thile file)

Create our Recline Dataset from sample local data

  var dataset = new recline.Model.Dataset({
+    records: sampleData
+  });

Custom template

+ +

Create a custom template for rendering the records

  var template = ' \
+    <div class="record"> \
+      <h3> \
+        {{title}} <em>by {{Author}}</em> \
+      </h3> \
+      <p>{{description}}</p> \
+      <p><code>${{price}}</code></p> \
+    </div> \
+  ';

Set up the search View (using custom template)

  var searchView = new SearchView({
+    el: $el,
+    model: dataset,
+    template: template 
+  });
+  searchView.render();

Optional - we configure the initial query a bit and set up facets

  dataset.queryState.set({
+      size: 10
+    },
+    {silent: true}
+  );
+  dataset.queryState.addFacet('Author');

Finally - now do the first query

+ +

After this point the Search View will take over handling queries!

  dataset.query();
+});

Simple Search View

+ +

This is a simple bespoke Backbone view for the Search. It Pulls together +various Recline UI components and the central Dataset and Query (state) +object

+ +

It also provides simple support for customization e.g. of template for list of results

+ +
 var view = new SearchView({
+   el: $('some-element'),
+   model: dataset
+   // EITHER a mustache template (passed a JSON version of recline.Model.Record
+   // OR a function which receives a record in JSON form and returns html
+   template: mustache-template-or-function
+ });
+
var SearchView = Backbone.View.extend({
+  initialize: function(options) {
+    this.el = $(this.el);
+    _.bindAll(this, 'render');
+    this.recordTemplate = options.template;

Every time we do a search the recline.Dataset.records Backbone +collection will get reset. We want to re-render each time!

    this.model.records.bind('reset', this.render);
+    this.templateResults = options.template;
+  },

overall template for this view

  template: ' \
+    <div class="controls"> \
+      <div class="query-here"></div> \
+    </div> \
+    <div class="total"><h2><span></span> records found</h2></div> \
+    <div class="body"> \
+      <div class="sidebar"></div> \
+      <div class="results"> \
+        {{{results}}} \
+      </div> \
+    </div> \
+    <div class="pager-here"></div> \
+  ',
+ 

render the view

  render: function() {
+    var results = '';
+    if (_.isFunction(this.templateResults)) {
+      var results = _.map(this.model.records.toJSON(), this.templateResults).join('\n');
+    } else {

templateResults is just for one result ...

      var tmpl = '{{#records}}' + this.templateResults + '{{/records}}'; 
+      var results = Mustache.render(tmpl, {
+        records: this.model.records.toJSON()
+      });
+    }
+    var html = Mustache.render(this.template, {
+      results: results
+    });
+    this.el.html(html);

Set the total records found info

    this.el.find('.total span').text(this.model.recordCount);

Now setup all the extra mini-widgets

+ +

Facets, Pager, QueryEditor etc

    var view = new recline.View.FacetViewer({
+      model: this.model
+    });
+    view.render();
+    this.el.find('.sidebar').append(view.el);
+
+    var pager = new recline.View.Pager({
+      model: this.model.queryState
+    });
+    this.el.find('.pager-here').append(pager.el);
+
+    var queryEditor = new recline.View.QueryEditor({
+      model: this.model.queryState
+    });
+    this.el.find('.query-here').append(queryEditor.el);
+  }
+});

+ +

Custom code very specific to this demo

e.g. to provide custom templates for the google doc and openspending examples

Handle case where we get data from a specific backend

+ +

Includes providing custom templates

function setupMoreComplexExample(config) {
+  var $el = $('.search-here');
+  var dataset = new recline.Model.Dataset(config);

async as may be fetching remote

  dataset.fetch().done(function() {
+    var template = templates[dataset.get('url')] || templates['generic'];
+    var searchView = new SearchView({
+      el: $el,
+      model: dataset,
+      template: template 
+    });
+    searchView.render();
+
+    dataset.queryState.set({
+        size: 5
+      },
+      {silent: true}
+    );
+    if (dataset.get('url') in templates) {

for gdocs example

      dataset.queryState.addFacet('cause');
+    }
+    dataset.query();
+  });
+};
+
+var templates = {

generic template function

  'generic': function(record) {
+    var template = '<div class="record"> \
+      <ul> \
+       {{#data}} \
+       <li>{{key}}: {{value}}</li> \
+       {{/data}} \
+     </ul> \
+    </div> \
+    ';
+    var data = _.map(_.keys(record), function(key) {
+      return { key: key, value: record[key] };
+    });
+    return Mustache.render(template, {
+      data: data
+    });
+  },
+  'http://openspending.org/api/search': function(record) {
+    record['time'] = record['time.label_facet']
+    var template = '<div class="record"> \
+      <h3> \
+        <a href="http://openspending.org/{{record.dataset}}/entries/{{record.id}}">{{record.dataset}} {{record.time}}</a> \
+        &ndash; <img src="http://openspending.org/static/img/icons/cd_16x16.png" /> {{amount_formatted}} \
+      </h3> \
+      <ul> \
+       {{#data}} \
+         <li>{{key}}: {{value}}</li> \
+       {{/data}} \
+       </ul> \
+    </div> \
+    ';
+    var data = [];
+    _.each(_.keys(record), function(key) {
+      if (key !='_id' && key != 'id') {
+        data.push({ key: key, value: record[key] });
+      }
+    });
+    return Mustache.render(template, {
+      record: record,
+      amount_formatted: formatAmount(record['amount']),
+      data: data
+    });
+  },
+  'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdExXSTl2Y01xZEszOTBFZjVzcGtzVVE': function(record) {
+    var template = '<div class="record"> \
+      <h3> \
+        {{record.incidentsite}} &ndash; {{record.datereported}} &ndash; {{record.estimatedspillvolumebbl}} barrels \
+      </h3> \
+      <ul> \
+       {{#data}} \
+         <li>{{key}}: {{value}}</li> \
+       {{/data}} \
+       </ul> \
+    </div> \
+    ';
+    var data = [];
+    _.each(_.keys(record), function(key) {
+      data.push({ key: key, value: record[key] });
+    });
+    return Mustache.render(template, {
+      record: record,
+      data: data
+    });
+  }
+}
+
+var sampleData = [
+  {
+    title: 'War and Peace',
+    description: 'The epic tale of love, war and history',
+    Author: 'Tolstoy',
+    price: 7.99
+  },
+  {
+    title: 'Anna Karenina',
+    description: 'How things go wrong in love and ultimately lead to suicide. This is why you should not have affairs, girls!',
+    Author: 'Tolstoy',
+    price: 8.50
+  },
+  {
+    title: "Fathers and Sons",
+    description: "Another 19th century Russian novel",
+    Author: "Turgenev",
+    price: 11
+  }
+];
+
+var formatAmount = function (num) {
+  var billion = 1000000000;
+  var million = 1000000;
+  var thousand = 1000;
+  var numabs = Math.abs(num);
+  if (numabs > billion) {
+    return (num / billion).toFixed(0) + 'bn';
+  }
+  if (numabs > (million / 2)) {
+    return (num / million).toFixed(0) + 'm';
+  }
+  if (numabs > thousand) {
+    return (num / thousand).toFixed(0) + 'k';
+  } else {
+    return num.toFixed(0);
+  }
+};
+
+
\ No newline at end of file