diff --git a/_includes/recline-deps.html b/_includes/recline-deps.html index 112121c6..2316cee3 100644 --- a/_includes/recline-deps.html +++ b/_includes/recline-deps.html @@ -50,6 +50,7 @@ + diff --git a/demos/search/app.js b/demos/search/app.js index 8e176330..343bbb6b 100644 --- a/demos/search/app.js +++ b/demos/search/app.js @@ -1,41 +1,66 @@ jQuery(function($) { var $el = $('.search-here'); - // var url = 'http://openspending.org/api/search'; - // var url = 'http://localhost:9200/tmp/sfpd-last-month'; + // 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) { + setupMoreComplexExample(config); + return; + } - // Crate our Recline Dataset - // Here we are just using the local data + // the simple example case + + // Create our Recline Dataset + // We'll just use some sample local data (see end of this file) var dataset = new recline.Model.Dataset({ - records: simpleData + records: sampleData }); - // Optional - // Let's configure the initial query a bit and set up facets - dataset.queryState.set({ - size: 10 - }, - {silent: true} - ); - dataset.queryState.addFacet('Author'); - dataset.query(); - - // The search view allows us to customize the template used to render the - // list of results - var template = getTemplate(); + // Set up the search View + + // We give it a custom template for rendering the example records + var template = ' \ +
\ +

\ + {{title}} by {{Author}} \ +

\ +

{{description}}

\ +

${{price}}

\ +
\ + '; 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'); + // Now do the first query + // After this point the Search View will take over handling queries + dataset.query(); }); + // 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 +// +// +// var view = new SearchView({ +// el: $('some-element'), +// model: dataset +// template: mustache-template-or-function +// }); var SearchView = Backbone.View.extend({ initialize: function(options) { this.el = $(this.el); @@ -57,11 +82,18 @@ var SearchView = Backbone.View.extend({ \
\ ', - + render: function() { - var results = Mustache.render(this.templateResults, { - records: this.model.records.toJSON() - }); + 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 }); @@ -86,24 +118,79 @@ var SearchView = Backbone.View.extend({ }); // -------------------------------------------------------- -// Stuff specific to this demo +// Stuff very specific to this demo -function getTemplate() { - template = ' \ - {{#records}} \ -
\ -

\ - {{title}} by {{Author}} \ -

\ -

{{description}}

\ -

${{price}}

\ -
\ - {{/records}} \ - '; - return template; -} +function setupMoreComplexExample(config) { + var $el = $('.search-here'); + var dataset = new recline.Model.Dataset(config); + // async as may be fetching remote + dataset.fetch().done(function() { + if (dataset.get('url').indexOf('openspending') === -1) { + // generic template function + var template = function(record) { + var template = '
\ +
\ + '; + var data = _.map(_.keys(record), function(key) { + return { key: key, value: record[key] }; + }); + return Mustache.render(template, { + data: data + }); + } + } else { + // generic template function + var template = function(record) { + record['time'] = record['time.label_facet'] + var template = '
\ +

\ + {{record.dataset}} {{record.time}} \ + – {{amount_formatted}} \ +

\ +
\ + '; + 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 + }); + } + } -var simpleData = [ + var searchView = new SearchView({ + el: $el, + model: dataset, + template: template + }); + searchView.render(); + + dataset.queryState.set({ + size: 10 + }, + {silent: true} + ); + if (dataset.get('url').indexOf('openspending') != -1) { + dataset.queryState.addFacet('dataset'); + } + dataset.query(); + }); +}; + +var sampleData = [ { title: 'War and Peace', description: 'The epic tale of love, war and history', @@ -124,3 +211,20 @@ var simpleData = [ } ]; +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); + } +}; diff --git a/demos/search/index.html b/demos/search/index.html index 3eb25414..aa94911c 100644 --- a/demos/search/index.html +++ b/demos/search/index.html @@ -81,7 +81,7 @@ ul.facet-items {

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

-

The default version uses some local example data but you can also connect directly to any other backend supported by Recline, in particular SOLR or ElasticSearch.

+

The default version uses some local example data but you can also connect directly to any other backend supported by Recline, in particular SOLR or ElasticSearch. For example, here's an example of this app running off the OpenSpending SOLR-style search API.


diff --git a/src/backend.solr.js b/src/backend.solr.js new file mode 100644 index 00000000..fae6d353 --- /dev/null +++ b/src/backend.solr.js @@ -0,0 +1,65 @@ +this.recline = this.recline || {}; +this.recline.Backend = this.recline.Backend || {}; +this.recline.Backend.Solr = this.recline.Backend.Solr || {}; + +(function($, my) { + my.__type__ = 'solr'; + + // ### fetch + // + // dataset must have a solr or url attribute pointing to solr endpoint + my.fetch = function(dataset) { + var jqxhr = $.ajax({ + url: dataset.solr || dataset.url, + data: { + rows: 1, + wt: 'json' + }, + dataType: 'jsonp', + jsonp: 'json.wrf' + }); + var dfd = $.Deferred(); + jqxhr.done(function(results) { + // if we get 0 results we cannot get fields + var fields = [] + if (results.response.numFound > 0) { + fields = _.map(_.keys(results.response.docs[0]), function(fieldName) { + return { id: fieldName }; + }); + } + var out = { + fields: fields, + useMemoryStore: false + }; + dfd.resolve(out); + }); + return dfd.promise(); + } + + // TODO - much work on proper query support is needed!! + my.query = function(queryObj, dataset) { + var q = queryObj.q || '*:*'; + var data = { + q: q, + rows: queryObj.size, + start: queryObj.from, + wt: 'json' + }; + var jqxhr = $.ajax({ + url: dataset.solr || dataset.url, + data: data, + dataType: 'jsonp', + jsonp: 'json.wrf' + }); + var dfd = $.Deferred(); + jqxhr.done(function(results) { + var out = { + total: results.response.numFound, + hits: results.response.docs + }; + dfd.resolve(out); + }); + return dfd.promise(); + }; + +}(jQuery, this.recline.Backend.Solr)); diff --git a/test/backend.solr.test.js b/test/backend.solr.test.js new file mode 100644 index 00000000..2b7ec65a --- /dev/null +++ b/test/backend.solr.test.js @@ -0,0 +1,85 @@ +(function ($) { +module("Backend SOLR"); + +test("fetch", function() { + var dataset = new recline.Model.Dataset({ + url: 'http://openspending.org/api/search', + backend: 'solr' + }); + // stop(); + + var stub = sinon.stub($, 'ajax', function(options) { + return { + done: function(callback) { + callback(sample_data); + return this; + }, + fail: function() { + } + }; + }); + + dataset.fetch().done(function(dataset) { + var exp = [ + "_id", + "amount", + "category.label_facet", + "dataset", + "from.label_facet", + "id", + "subcategory.label_facet", + "time.label_facet", + "to.label_facet" + ]; + deepEqual( + exp, + _.pluck(dataset.fields.toJSON(), 'id') + ); + // check we've mapped types correctly + equal(dataset.fields.get('amount').get('type'), 'string'); + + // fetch does a query so we can check for records + equal(dataset.recordCount, 10342132); + equal(dataset.records.length, 2); + equal(dataset.records.at(0).get('id'), '3e3e25d7737634127b76d5ee4a7df280987013c7'); + // start(); + }); + $.ajax.restore(); +}); + +var sample_data = { + "response": { + "docs": [ + { + "_id": "south-african-national-gov-budget-2012-13::3e3e25d7737634127b76d5ee4a7df280987013c7", + "amount": 30905738200000.0, + "category.label_facet": "General public services", + "dataset": "south-african-national-gov-budget-2012-13", + "from.label_facet": "National Treasury", + "id": "3e3e25d7737634127b76d5ee4a7df280987013c7", + "subcategory.label_facet": "Transfers of a general character between different levels of government", + "time.label_facet": "01. April 2012", + "to.label_facet": "Provincial Equitable Share" + }, + { + "_id": "south-african-national-gov-budget-2012-13::738849e28e6b3c45e5b0001e142b51479b3a3e41", + "amount": 8938807300000.0, + "category.label_facet": "General public services", + "dataset": "south-african-national-gov-budget-2012-13", + "from.label_facet": "National Treasury", + "id": "738849e28e6b3c45e5b0001e142b51479b3a3e41", + "subcategory.label_facet": "Public debt transactions", + "time.label_facet": "01. April 2012", + "to.label_facet": "State Debt Costs" + } + ], + "numFound": 10342132, + "start": 0 + }, + "responseHeader": { + "QTime": 578, + "status": 0 + } +}; + +})(this.jQuery); diff --git a/test/index.html b/test/index.html index 6d3463f9..983b4749 100644 --- a/test/index.html +++ b/test/index.html @@ -35,6 +35,7 @@ + @@ -43,6 +44,7 @@ +