From 7709da98fc6836d85de563888d38a750ea43275f Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Mon, 15 Oct 2012 19:43:54 +0100 Subject: [PATCH 1/5] [build][s]: regular build. --- dist/recline.dataset.js | 53 ++++++++++--- dist/recline.js | 172 ++++++++++++++++++++++++++++------------ 2 files changed, 164 insertions(+), 61 deletions(-) diff --git a/dist/recline.dataset.js b/dist/recline.dataset.js index 86359d90..dd15f128 100644 --- a/dist/recline.dataset.js +++ b/dist/recline.dataset.js @@ -380,6 +380,9 @@ my.Field = Backbone.Model.extend({ if (this.attributes.label === null) { this.set({label: this.id}); } + if (this.attributes.type.toLowerCase() in this._typeMap) { + this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()]; + } if (options) { this.renderer = options.renderer; this.deriver = options.deriver; @@ -389,6 +392,17 @@ my.Field = Backbone.Model.extend({ } this.facets = new my.FacetList(); }, + _typeMap: { + 'text': 'string', + 'double': 'number', + 'float': 'number', + 'numeric': 'number', + 'int': 'integer', + 'datetime': 'date-time', + 'bool': 'boolean', + 'timestamp': 'date-time', + 'json': 'object' + }, defaultRenderers: { object: function(val, field, doc) { return JSON.stringify(val); @@ -396,7 +410,7 @@ my.Field = Backbone.Model.extend({ geo_point: function(val, field, doc) { return JSON.stringify(val); }, - 'float': function(val, field, doc) { + 'number': function(val, field, doc) { var format = field.get('format'); if (format === 'percentage') { return val + '%'; @@ -470,16 +484,15 @@ my.Query = Backbone.Model.extend({ } } }, - // ### addFilter + // ### addFilter(filter) // - // Add a new filter (appended to the list of filters) + // Add a new filter specified by the filter hash and append to the list of filters // // @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates addFilter: function(filter) { // crude deep copy var ourfilter = JSON.parse(JSON.stringify(filter)); - // not full specified so use template and over-write - // 3 as for 'type', 'field' and 'fieldType' + // not fully specified so use template and over-write if (_.keys(filter).length <= 3) { ourfilter = _.extend(this._filterTemplates[filter.type], ourfilter); } @@ -595,7 +608,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } else { if (data) { this.fields = _.map(data[0], function(value, key) { - return {id: key}; + return {id: key, type: 'string'}; }); } } @@ -670,10 +683,20 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; geo_distance : geo_distance }; var dataParsers = { - number : function (e) { return parseFloat(e, 10); }, + 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() } + date : function (e) { return new Date(e).valueOf() }, + datetime : function (e) { return new Date(e).valueOf() } }; + var keyedFields = {}; + _.each(self.fields, function(field) { + keyedFields[field.id] = field; + }); + function getDataParser(filter) { + var fieldType = keyedFields[filter.field].type || 'string'; + return dataParsers[fieldType]; + } // filter records return _.filter(results, function (record) { @@ -686,9 +709,8 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; }); // filters definitions - function term(record, filter) { - var parse = dataParsers[filter.fieldType]; + var parse = getDataParser(filter); var value = parse(record[filter.field]); var term = parse(filter.term); @@ -696,12 +718,19 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } function range(record, filter) { - var parse = dataParsers[filter.fieldType]; + 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); - return (value >= start && value <= 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() { diff --git a/dist/recline.js b/dist/recline.js index a0bbbd77..083279fc 100644 --- a/dist/recline.js +++ b/dist/recline.js @@ -9,17 +9,38 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; // // General notes // + // We need 2 things to make most requests: + // + // 1. CKAN API endpoint + // 2. ID of resource for which request is being made + // + // There are 2 ways to specify this information. + // + // EITHER (checked in order): + // // * Every dataset must have an id equal to its resource id on the CKAN instance - // * You should set the CKAN API endpoint for requests by setting API_ENDPOINT value on this module (recline.Backend.Ckan.API_ENDPOINT) + // * The dataset has an endpoint attribute pointing to the CKAN API endpoint + // + // OR: + // + // Set the url attribute of the dataset to point to the Resource on the CKAN instance. The endpoint and id will then be automatically computed. my.__type__ = 'ckan'; // Default CKAN API endpoint used for requests (you can change this but it will affect every request!) + // + // DEPRECATION: this will be removed in v0.7. Please set endpoint attribute on dataset instead my.API_ENDPOINT = 'http://datahub.io/api'; // ### fetch my.fetch = function(dataset) { - var wrapper = my.DataStore(); + if (dataset.endpoint) { + var wrapper = my.DataStore(dataset.endpoint); + } else { + var out = my._parseCkanResourceUrl(dataset.url); + dataset.id = out.resource_id; + var wrapper = my.DataStore(out.endpoint); + } var dfd = $.Deferred(); var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0}); jqxhr.done(function(results) { @@ -55,8 +76,14 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; } my.query = function(queryObj, dataset) { + if (dataset.endpoint) { + var wrapper = my.DataStore(dataset.endpoint); + } else { + var out = my._parseCkanResourceUrl(dataset.url); + dataset.id = out.resource_id; + var wrapper = my.DataStore(out.endpoint); + } var actualQuery = my._normalizeQuery(queryObj, dataset); - var wrapper = my.DataStore(); var dfd = $.Deferred(); var jqxhr = wrapper.search(actualQuery); jqxhr.done(function(results) { @@ -89,15 +116,24 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; } return that; - } + }; + + // Parse a normal CKAN resource URL and return API endpoint etc + // + // Normal URL is something like http://demo.ckan.org/dataset/some-dataset/resource/eb23e809-ccbb-4ad1-820a-19586fc4bebd + my._parseCkanResourceUrl = function(url) { + parts = url.split('/'); + var len = parts.length; + return { + resource_id: parts[len-1], + endpoint: parts.slice(0,[len-4]).join('/') + '/api' + } + }; var CKAN_TYPES_MAP = { 'int4': 'integer', 'int8': 'integer', - 'float8': 'float', - 'text': 'string', - 'json': 'object', - 'timestamp': 'date' + 'float8': 'float' }; }(jQuery, this.recline.Backend.Ckan)); @@ -917,7 +953,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } else { if (data) { this.fields = _.map(data[0], function(value, key) { - return {id: key}; + return {id: key, type: 'string'}; }); } } @@ -992,10 +1028,20 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; geo_distance : geo_distance }; var dataParsers = { - number : function (e) { return parseFloat(e, 10); }, + 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() } + date : function (e) { return new Date(e).valueOf() }, + datetime : function (e) { return new Date(e).valueOf() } }; + var keyedFields = {}; + _.each(self.fields, function(field) { + keyedFields[field.id] = field; + }); + function getDataParser(filter) { + var fieldType = keyedFields[filter.field].type || 'string'; + return dataParsers[fieldType]; + } // filter records return _.filter(results, function (record) { @@ -1008,9 +1054,8 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; }); // filters definitions - function term(record, filter) { - var parse = dataParsers[filter.fieldType]; + var parse = getDataParser(filter); var value = parse(record[filter.field]); var term = parse(filter.term); @@ -1018,12 +1063,19 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } function range(record, filter) { - var parse = dataParsers[filter.fieldType]; + 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); - return (value >= start && value <= 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() { @@ -1628,6 +1680,9 @@ my.Field = Backbone.Model.extend({ if (this.attributes.label === null) { this.set({label: this.id}); } + if (this.attributes.type.toLowerCase() in this._typeMap) { + this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()]; + } if (options) { this.renderer = options.renderer; this.deriver = options.deriver; @@ -1637,6 +1692,17 @@ my.Field = Backbone.Model.extend({ } this.facets = new my.FacetList(); }, + _typeMap: { + 'text': 'string', + 'double': 'number', + 'float': 'number', + 'numeric': 'number', + 'int': 'integer', + 'datetime': 'date-time', + 'bool': 'boolean', + 'timestamp': 'date-time', + 'json': 'object' + }, defaultRenderers: { object: function(val, field, doc) { return JSON.stringify(val); @@ -1644,7 +1710,7 @@ my.Field = Backbone.Model.extend({ geo_point: function(val, field, doc) { return JSON.stringify(val); }, - 'float': function(val, field, doc) { + 'number': function(val, field, doc) { var format = field.get('format'); if (format === 'percentage') { return val + '%'; @@ -1718,16 +1784,15 @@ my.Query = Backbone.Model.extend({ } } }, - // ### addFilter + // ### addFilter(filter) // - // Add a new filter (appended to the list of filters) + // Add a new filter specified by the filter hash and append to the list of filters // // @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates addFilter: function(filter) { // crude deep copy var ourfilter = JSON.parse(JSON.stringify(filter)); - // not full specified so use template and over-write - // 3 as for 'type', 'field' and 'fieldType' + // not fully specified so use template and over-write if (_.keys(filter).length <= 3) { ourfilter = _.extend(this._filterTemplates[filter.type], ourfilter); } @@ -1964,7 +2029,8 @@ my.Graph = Backbone.View.extend({ var xfield = self.model.fields.get(self.state.attributes.group); // time series - var isDateTime = xfield.get('type') === 'date'; + var xtype = xfield.get('type'); + var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); if (self.model.records.models[parseInt(x)]) { x = self.model.records.models[parseInt(x)].get(self.state.attributes.group); @@ -2078,7 +2144,8 @@ my.Graph = Backbone.View.extend({ var x = doc.getFieldValue(xfield); // time series - var isDateTime = xfield.get('type') === 'date'; + var xtype = xfield.get('type'); + var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); if (isDateTime) { // datetime @@ -3770,7 +3837,7 @@ my.SlickGrid = Backbone.View.extend({ }); // Order them if there is ordering info on the state - if (this.state.get('columnsOrder')){ + if (this.state.get('columnsOrder') && this.state.get('columnsOrder').length > 0) { visibleColumns = visibleColumns.sort(function(a,b){ return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; }); @@ -4301,20 +4368,27 @@ this.recline.View = this.recline.View || {}; (function($, my) { +// ## FacetViewer +// +// Widget for displaying facets +// +// Usage: +// +// var viewer = new FacetViewer({ +// model: dataset +// }); my.FacetViewer = Backbone.View.extend({ - className: 'recline-facet-viewer well', + className: 'recline-facet-viewer', template: ' \ - × \ -
\ -
\ -

Facets

\ -
\ +
\ {{#facets}} \ -