Base class for backends providing a template and convenience functions.
-You do not have to inherit from this class but even when not it does
-provide guidance on the functions you must implement.
-
-
Note also that while this (and other Backends) are implemented as Backbone models this is just a convenience.
'type' of this backend. This should be either the class path for this
object as a string (e.g. recline.Backend.Memory) or for Backends within
recline.Backend module it may be their class name.
This value is used as an identifier for this backend when initializing
-backends (see recline.Model.Dataset.initialize).
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,
@@ -79,8 +69,8 @@ details):
Convenience method providing a crude way to catch backend errors on JSONP calls.
-Many of backends use JSONP and so will not get error messages and this is
-a crude way to catch those errors.
_wrapInTimeout:function(ourFunction){
- vardfd=$.Deferred();
- vartimeout=5000;
- vartimer=setTimeout(function(){
- dfd.reject({
- message:'Request Error: Backend did not respond after '+(timeout/1000)+' seconds'
+
\ No newline at end of file
diff --git a/docs/backend/localcsv.html b/docs/backend/csv.html
similarity index 76%
rename from docs/backend/localcsv.html
rename to docs/backend/csv.html
index 3c708f28..54474a6e 100644
--- a/docs/backend/localcsv.html
+++ b/docs/backend/csv.html
@@ -1,15 +1,20 @@
- localcsv.js
Converts a Comma Separated Values string into an array of arrays.
Each line in the CSV becomes an array.
Empty fields are converted to nulls and non-quoted numbers are converted to integers or floats.
@@ -46,7 +51,7 @@ Each line in the CSV becomes an array.
@param {Boolean} [trim=false] If set to True leading and trailing whitespace is stripped off of each non-quoted field as it is imported
@param {String} [separator=','] Separator for CSV file
Heavily based on uselesscode's JS CSV parser (MIT Licensed):
-thttp://www.uselesscode.org/javascript/csv/
\ No newline at end of file
diff --git a/docs/backend/dataproxy.html b/docs/backend/dataproxy.html
index 0ff29c43..e5229396 100644
--- a/docs/backend/dataproxy.html
+++ b/docs/backend/dataproxy.html
@@ -1,11 +1,13 @@
- dataproxy.js
Convenience method providing a crude way to catch backend errors on JSONP calls.
+Many of backends use JSONP and so will not get error messages and this is
+a crude way to catch those errors.
\ No newline at end of file
diff --git a/docs/backend/elasticsearch.html b/docs/backend/elasticsearch.html
index 11bb4b3a..ffd2f047 100644
--- a/docs/backend/elasticsearch.html
+++ b/docs/backend/elasticsearch.html
@@ -1,122 +1,81 @@
- elasticsearch.js
-var backend = new recline.Backend.ElasticSearch({
- // optional as can also be provided by Dataset/Document
- url: {url to ElasticSearch endpoint i.e. ES 'type/table' url - more info below}
- // optional
- headers: {dict of headers to add to each request}
-});
-
-@param {String} url: url for ElasticSearch type/table, e.g. for ES running
-on localhost:9200 with index // twitter and type tweet it would be:
+
Connecting to ElasticSearch endpoints.
+@param {String} endpoint: url for ElasticSearch type/table, e.g. for ES running
+on localhost:9200 with index // twitter and type tweet it would be:
http://localhost:9200/twitter/tweet
-This url is optional since the ES endpoint url may be specified on the the
-dataset (and on a Document by the document having a dataset attribute) by
-having one of the following (see also `_getESUrl` function):
+
create / update a document to ElasticSearch backend
@param {Object} doc an object to insert to the index.
-@param {string} url (optional) url for ElasticSearch endpoint (if not
-defined called this._getESUrl()
upsert:function(doc,url){
+@return deferred supporting promise API
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
+url attribute.
\ No newline at end of file
diff --git a/docs/backend/gdocs.html b/docs/backend/gdocs.html
index b84397fa..e014d57d 100644
--- a/docs/backend/gdocs.html
+++ b/docs/backend/gdocs.html
@@ -1,5 +1,6 @@
- gdocs.js
zip the fields with the data rows to produce js objs
+TODO: factor this out as a common method with other backends
varobjs=_.map(data,function(d){varobj={};_.each(_.zip(fields,d),function(x){obj[x[0]]=x[1];});returnobj;});
- dfd.resolve(this._docsToQueryResult(objs));
- returndfd;
- },
- gdocsToJavascript:function(gdocsSpreadsheet){
- /*
- :options: (optional) optional argument dictionary:
- columnsToUse: list of columns to use (specified by field names)
- colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion).
- :return: tabular data object (hash with keys: field and data).
-
- Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.
- */
- varoptions={};
- if(arguments.length>1){
- options=arguments[1];
+ varout={
+ total:objs.length,
+ hits:_.map(objs,function(row){
+ return{_source:row}
+ })}
- varresults={
- 'field':[],
- 'data':[]
- };
Parse data from Google Docs API into a reasonable form
+
+
:options: (optional) optional argument dictionary:
+columnsToUse: list of columns to use (specified by field names)
+colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion).
+:return: tabular data object (hash with keys: field and data).
+
+
Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows.
\ No newline at end of file
diff --git a/docs/backend/memory.html b/docs/backend/memory.html
index e1c865a7..a1a95921 100644
--- a/docs/backend/memory.html
+++ b/docs/backend/memory.html
@@ -1,5 +1,6 @@
- memory.js
@@ -12,127 +13,66 @@ as per recline.Model.Field). If fields not specified they will be taken
from the data.
@param metadata: (optional) dataset metadata - see recline.Model.Dataset.
If not defined (or id not provided) id will be autogenerated.
this._applyFilters=function(results,queryObj){_.each(queryObj.filters,function(filter){results=_.filter(results,function(doc){varfieldId=_.keys(filter.term)[0];
@@ -140,14 +80,14 @@ If not defined (or id not provided) id will be autogenerated.
});});returnresults;
- },
\ No newline at end of file
diff --git a/docs/model.html b/docs/model.html
index 3009088a..2fd24970 100644
--- a/docs/model.html
+++ b/docs/model.html
@@ -115,7 +115,7 @@ also returned.
if(recline&&recline.Backend){_.each(_.keys(recline.Backend),function(name){if(name.toLowerCase()===backendString.toLowerCase()){
- backend=newrecline.Backend[name]();
+ backend=newrecline.Backend[name].Backbone();}});}
@@ -133,19 +133,19 @@ Dataset is an Object like:
// convenience - if url provided and dataste not this be used as dataset url
url: {dataset url}
...
-}
hack-y - restoring a memory dataset does not mean much ...
if(state.backend==='memory'){
+ dataset=recline.Backend.Memory.createDataset([{stub:'this is a stub dataset because we do not restore memory datasets'}],[],state.dataset// metadata);}else{
+ vardatasetInfo={
+ url:state.url
+ };dataset=newrecline.Model.Dataset(
- state.dataset,
+ datasetInfo,state.backend);}
@@ -183,7 +183,6 @@ for this document.
format: (optional) used to indicate how the data should be formatted. For example:
type=date, format=yyyy-mm-dd
type=float, format=percentage
-
type=string, format=link (render as hyperlink)
type=string, format=markdown (render as markdown if Showdown available)
is_derived: (default: false) attribute indicating this field has no backend data but is just derived from other fields (see below).
@@ -202,7 +201,18 @@ document, 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
-value of this field prior to rendering.
defaults:{label:null,type:'string',format:null,
@@ -238,9 +248,7 @@ value of this field prior to rendering.
},
'string':function(val,field,doc){varformat=field.get('format');
- if(format==='link'){
- return'<a href="VAL">VAL</a>'.replace(/VAL/g,val);
- }elseif(format==='markdown'){
+ if(format==='markdown'){if(typeofShowdown!=='undefined'){varshowdown=newShowdown.converter();out=showdown.makeHtml(val);
@@ -248,15 +256,21 @@ value of this field prior to rendering.
Object to store Facet information, that is summary information (e.g. values
and counts) about a field obtained by some faceting method on the
@@ -407,14 +421,16 @@ key used to specify this facet in the facet query):
Backends will register themselves by id into this registry
my.backends={};
+
Override Backbone.sync to hand off to sync function in relevant backend
Backbone.sync=function(method,model,options){
+ returnmodel.backend.sync(method,model,options);
+};}(jQuery,this.recline.Model));
diff --git a/docs/view-graph.html b/docs/view-graph.html
index e71795ba..2f457a7f 100644
--- a/docs/view-graph.html
+++ b/docs/view-graph.html
@@ -26,13 +26,6 @@ generate the element itself (you can then append view.el to the DOM.
template:' \ <div class="editor"> \
- <div class="editor-info editor-hide-info"> \
- <h3 class="action-toggle-help">Help »</h3> \
- <p>To create a chart select a column (group) to use as the x-axis \
- then another column (Series A) to plot against it.</p> \
- <p>You can add add \
- additional series by clicking the "Add series" button</p> \
- </div> \ <form class="form-stacked"> \ <div class="clearfix"> \ <label>Graph Type</label> \
@@ -92,8 +85,7 @@ generate the element itself (you can then append view.el to the DOM.
events:{'change form select':'onEditorSubmit','click .editor-add':'_onAddSeries',
- 'click .action-remove-series':'removeSeries',
- 'click .action-toggle-help':'toggleHelp'
+ 'click .action-remove-series':'removeSeries'},initialize:function(options){
@@ -123,7 +115,7 @@ generate the element itself (you can then append view.el to the DOM.
render:function(){varself=this;vartmplData=this.model.toTemplateJSON();
- varhtmls=$.mustache(this.template,tmplData);
+ varhtmls=Mustache.render(this.template,tmplData);$(this.el).html(htmls);this.$graph=this.el.find('.panel.graph');
if(this.state.get('graphType')){this._selectOption('.editor-type',this.state.get('graphType'));
@@ -342,7 +334,7 @@ have no field type info). Thus at present we only do this for bars.
seriesName:String.fromCharCode(idx+64+1),},this.model.toTemplateJSON());
- varhtmls=$.mustache(this.templateSeriesEditor,data);
+ varhtmls=Mustache.render(this.templateSeriesEditor,data);this.el.find('.editor-series-group').append(htmls);returnthis;},
@@ -357,11 +349,7 @@ have no field type info). Thus at present we only do this for bars.
var$el=$(e.target);$el.parent().parent().remove();this.onEditorSubmit();
- },
-
- toggleHelp:function(){
- this.el.find('.editor-info').toggleClass('editor-hide-info');
- },
+ }});})(jQuery,recline.View);
diff --git a/docs/view-grid.html b/docs/view-grid.html
index 2c57e2cc..af6d95e9 100644
--- a/docs/view-grid.html
+++ b/docs/view-grid.html
@@ -14,7 +14,7 @@
initialize:function(modelEtc){varself=this;this.el=$(this.el);
- _.bindAll(this,'render');
+ _.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);
@@ -30,8 +30,8 @@
'click .column-header-menu .data-table-menu li a':'onColumnHeaderClick','click .row-header-menu':'onRowHeaderClick','click .root-header-menu':'onRootHeaderClick',
- 'click .data-table-menu li a':'onMenuClick'
- },
view.bind('recline:flash',function(flash){self.trigger('recline:flash',flash);});view.state=this.tempState;
@@ -105,7 +105,7 @@ from DOM) while id may be int
add the remainder to the first field width so we make up full col
if(idx==0){
+ field.set({width:width+remainder});
+ }else{
+ field.set({width:width});
+ }
+ });
+ varhtmls=Mustache.render(this.template,this.toTemplateJSON());this.el.html(htmls);this.model.currentDocuments.forEach(function(doc){vartr=$('<tr />');
@@ -176,11 +196,24 @@ from DOM) while id may be int
For each document passed, a GeoJSON geometry will be extracted and added
to the features layer. If an exception is thrown, the process will be
@@ -252,15 +243,15 @@ stopped and an error notification shown.
We'll create a GeoJSON like point object from the two lat/lon fields
varlon=doc.get(this.state.get('lonField'));varlat=doc.get(this.state.get('latField'));if(!isNaN(parseFloat(lon))&&!isNaN(parseFloat(lat))){return{
@@ -313,12 +307,12 @@ link this Leaflet layer to a Recline doc
This will be available in the next Leaflet stable release.
In the meantime we add it manually to our layer.
this.features.getBounds=function(){varbounds=newL.LatLngBounds();this._iterateLayers(function(layer){
@@ -387,7 +381,7 @@ In the meantime we add it manually to our layer.
Private: Helper function to select an option from a select list
_selectOption:function(id,value){varoptions=$('.'+id+' > select > option');if(options){options.each(function(opt){
diff --git a/docs/view.html b/docs/view.html
index dcb33dd2..317e2ada 100644
--- a/docs/view.html
+++ b/docs/view.html
@@ -74,6 +74,23 @@ url or elsewhere and reloading of application from a stored state.
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.
@@ -200,6 +217,13 @@ initialized the DataExplorer with the relevant views themselves.
model:this.model,state:this.state.get('view-map')}),
+ },{
+ id:'timeline',
+ label:'Timeline',
+ view:newmy.Timeline({
+ model:this.model,
+ state:this.state.get('view-timeline')
+ }),}];}
this.render();this._bindStateChanges();
@@ -213,12 +237,11 @@ initialized the DataExplorer with the relevant views themselves.
}this.model.bind('query:start',function(){
- self.notify({message:'Loading data',loader:true});
+ self.notify({loader:true,persist:true});});this.model.bind('query:done',function(){self.clearNotifications();self.el.find('.doc-count').text(self.model.docCount||'Unknown');
- self.notify({message:'Data loaded',category:'success'});});this.model.bind('query:fail',function(error){self.clearNotifications();
@@ -253,7 +276,7 @@ note this.model and dataset returned are the same
render:function(){vartmplData=this.model.toTemplateJSON();tmplData.views=this.pageViews;
- vartemplate=$.mustache(this.template,tmplData);
+ vartemplate=Mustache.render(this.template,tmplData);$(this.el).html(template);var$dataViewContainer=this.el.find('.data-view-container');_.each(this.pageViews,function(view,pageName){
@@ -318,7 +341,7 @@ note this.model and dataset returned are the same
query:query,'view-graph':graphState,backend:this.model.backend.__type__,
- dataset:this.model.toJSON(),
+ url:this.model.get('url'),currentView:null,readOnly:false},
@@ -363,19 +386,25 @@ flash object. Flash attributes (all are optional):
diff --git a/recline.js b/recline.js
index 5672ee47..51a9bc23 100644
--- a/recline.js
+++ b/recline.js
@@ -201,7 +201,7 @@ my.Dataset = Backbone.Model.extend({
if (recline && recline.Backend) {
_.each(_.keys(recline.Backend), function(name) {
if (name.toLowerCase() === backendString.toLowerCase()) {
- backend = new recline.Backend[name]();
+ backend = new recline.Backend[name].Backbone();
}
});
}
@@ -224,20 +224,20 @@ my.Dataset = Backbone.Model.extend({
// ...
// }
my.Dataset.restore = function(state) {
- // hack-y - restoring a memory dataset does not mean much ...
var dataset = null;
- if (state.url && !state.dataset) {
- state.dataset = {url: state.url};
- }
+ // hack-y - restoring a memory dataset does not mean much ...
if (state.backend === 'memory') {
- dataset = recline.Backend.createDataset(
+ dataset = recline.Backend.Memory.createDataset(
[{stub: 'this is a stub dataset because we do not restore memory datasets'}],
[],
state.dataset // metadata
);
} else {
+ var datasetInfo = {
+ url: state.url
+ };
dataset = new recline.Model.Dataset(
- state.dataset,
+ datasetInfo,
state.backend
);
}
@@ -285,7 +285,6 @@ my.DocumentList = Backbone.Collection.extend({
// * format: (optional) used to indicate how the data should be formatted. For example:
// * type=date, format=yyyy-mm-dd
// * type=float, format=percentage
-// * type=string, format=link (render as hyperlink)
// * type=string, format=markdown (render as markdown if Showdown available)
// * is_derived: (default: false) attribute indicating this field has no backend data but is just derived from other fields (see below).
//
@@ -304,6 +303,15 @@ my.DocumentList = Backbone.Collection.extend({
// 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
// value of this field prior to rendering.
+//
+// #### Default renderers
+//
+// * string
+// * no format provided: pass through but convert http:// to hyperlinks
+// * format = plain: do no processing on the source text
+// * format = markdown: process as markdown (if Showdown library available)
+// * float
+// * format = percentage: format as a percentage
my.Field = Backbone.Model.extend({
// ### defaults - define default values
defaults: {
@@ -346,9 +354,7 @@ my.Field = Backbone.Model.extend({
},
'string': function(val, field, doc) {
var format = field.get('format');
- if (format === 'link') {
- return 'VAL'.replace(/VAL/g, val);
- } else if (format === 'markdown') {
+ if (format === 'markdown') {
if (typeof Showdown !== 'undefined') {
var showdown = new Showdown.converter();
out = showdown.makeHtml(val);
@@ -356,8 +362,16 @@ my.Field = Backbone.Model.extend({
} else {
return val;
}
+ } else if (format == 'plain') {
+ return val;
+ } else {
+ // as this is the default and default type is string may get things
+ // here that are not actually strings
+ if (val && typeof val === 'string') {
+ val = val.replace(/(https?:\/\/[^ ]+)/g, '$1');
+ }
+ return val
}
- return val;
}
}
});
@@ -547,10 +561,12 @@ my.ObjectState = Backbone.Model.extend({
});
-// ## Backend registry
+// ## Backbone.sync
//
-// Backends will register themselves by id into this registry
-my.backends = {};
+// Override Backbone.sync to hand off to sync function in relevant backend
+Backbone.sync = function(method, model, options) {
+ return model.backend.sync(method, model, options);
+};
}(jQuery, this.recline.Model));
@@ -662,13 +678,6 @@ my.Graph = Backbone.View.extend({
template: ' \
\
-
\
-
Help »
\
-
To create a chart select a column (group) to use as the x-axis \
- then another column (Series A) to plot against it.
\
-
You can add add \
- additional series by clicking the "Add series" button