From 72e444c5ca380a8c2ce81f2ce4f3129bb06010dd Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 16 Jun 2012 17:17:33 +0100 Subject: [PATCH] [build][s]: . --- dist/recline.js | 333 +++++++++++++++++++++++++++++++----------------- 1 file changed, 216 insertions(+), 117 deletions(-) diff --git a/dist/recline.js b/dist/recline.js index b0d1ca64..4fb96e26 100644 --- a/dist/recline.js +++ b/dist/recline.js @@ -461,7 +461,7 @@ my.FieldList = Backbone.Collection.extend({ // may just pass this straight through e.g. for an SQL backend this could be // the full SQL query // -// * filters: dict of ElasticSearch filters. These will be and-ed together for +// * filters: array of ElasticSearch filters. These will be and-ed together for // execution. // // **Examples** @@ -479,12 +479,44 @@ my.Query = Backbone.Model.extend({ return { size: 100, from: 0, + q: '', facets: {}, - // - // , filter: {} filters: [] }; }, + _filterTemplates: { + term: { + type: 'term', + field: '', + term: '' + }, + geo_distance: { + distance: 10, + distance_unit: 'km', + point: { + lon: 0, + lat: 0 + } + } + }, + // ### addFilter + // + // Add a new filter (appended 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 + if (_.keys(filter).length <= 2) { + ourfilter = _.extend(this._filterTemplates[filter.type], ourfilter); + } + var filters = this.get('filters'); + filters.push(ourfilter); + this.trigger('change:filters:new-blank'); + }, + updateFilter: function(index, value) { + }, // #### addTermFilter // // Set (update or add) a terms filter to filters @@ -504,6 +536,22 @@ my.Query = Backbone.Model.extend({ this.trigger('change:filters:new-blank'); } }, + addGeoDistanceFilter: function(field) { + var filters = this.get('filters'); + var filter = { + geo_distance: { + distance: '10km', + } + }; + filter.geo_distance[field] = { + 'lon': 0, + 'lat': 0 + }; + filters.push(filter); + this.set({filters: filters}); + // adding a new blank filter and do not want to trigger a new query + this.trigger('change:filters:new-blank'); + }, // ### removeFilter // // Remove a filter from filters at index filterIndex @@ -1650,38 +1698,55 @@ my.Map = Backbone.View.extend({ // Private: Return a GeoJSON geomtry extracted from the record fields // _getGeometryFromRecord: function(doc){ - if (this._geomReady()){ - if (this.state.get('geomField')){ - var value = doc.get(this.state.get('geomField')); - if (typeof(value) === 'string'){ - // We *may* have a GeoJSON string representation - try { - value = $.parseJSON(value); - } catch(e) { - } - } - 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')); - var lat = doc.get(this.state.get('latField')); - if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) { - return { - type: 'Point', - coordinates: [lon,lat] - }; - } + if (this.state.get('geomField')){ + var value = doc.get(this.state.get('geomField')); + if (typeof(value) === 'string'){ + // We *may* have a GeoJSON string representation + try { + value = $.parseJSON(value); + } catch(e) {} + } + + if (typeof(value) === 'string') { + value = value.replace('(', '').replace(')', ''); + var parts = value.split(','); + var lat = parseFloat(parts[0]); + var lon = parseFloat(parts[1]); + if (!isNaN(lon) && !isNaN(parseFloat(lat))) { + return { + "type": "Point", + "coordinates": [lon, lat] + }; + } else { + return null; + } + } else if (value && value.slice) { + // [ lon, lat ] + return { + "type": "Point", + "coordinates": [value[0], value[1]] + }; + } else if (value && value.lat) { + // of form { lat: ..., lon: ...} + return { + "type": "Point", + "coordinates": [value.lon || value.lng, value.lat] + }; + } + // We o/w 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')); + var lat = doc.get(this.state.get('latField')); + if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) { + return { + type: 'Point', + coordinates: [lon,lat] + }; } - return null; } + return null; }, // Private: Check if there is a field with GeoJSON geometries or alternatively, @@ -2568,35 +2633,18 @@ my.SlickGrid = Backbone.View.extend({ this.grid = new Slick.Grid(this.el, data, visibleColumns, options); // Column sorting - var gridSorter = function(field, ascending, grid, data){ - - data.sort(function(a, b){ - var result = - a[field] > b[field] ? 1 : - a[field] < b[field] ? -1 : - 0 - ; - return ascending ? result : -result; - }); - - grid.setData(data); - grid.updateRowCount(); - grid.render(); - } - - var sortInfo = this.state.get('columnsSort'); + var sortInfo = this.model.queryState.get('sort'); if (sortInfo){ - var sortAsc = !(sortInfo['direction'] == 'desc'); - gridSorter(sortInfo.column, sortAsc, self.grid, data); - this.grid.setSortColumn(sortInfo.column, sortAsc); + var column = _.keys(sortInfo[0])[0]; + var sortAsc = !(sortInfo[0][column].order == 'desc'); + this.grid.setSortColumn(column, sortAsc); } this.grid.onSort.subscribe(function(e, args){ - gridSorter(args.sortCol.field,args.sortAsc,self.grid,data); - self.state.set({columnsSort:{ - column:args.sortCol, - direction: (args.sortAsc) ? 'asc':'desc' - }}); + var order = (args.sortAsc) ? 'asc':'desc'; + var sort = [{}]; + sort[0][args.sortCol.field] = {order: order}; + self.model.query({sort: sort}); }); this.grid.onColumnsReordered.subscribe(function(e, args){ @@ -2756,7 +2804,9 @@ this.recline.View = this.recline.View || {}; (function($, my) { // turn off unnecessary logging from VMM Timeline -VMM.debug = false; +if (typeof VMM !== 'undefined') { + VMM.debug = false; +} // ## Timeline // @@ -3313,7 +3363,8 @@ my.FilterEditor = Backbone.View.extend({
\ \ \ \ \ - × \ - \ - \ - {{/termFilters}} \ - {{#termFilters.length}} \ + {{#filters}} \ + {{{filterRender}}} \ + {{/filters}} \ + {{#filters.length}} \ \ - {{/termFilters.length}} \ + {{/filters.length}} \ \ \ ', + filterTemplates: { + term: ' \ +
\ +
\ + \ + {{field}} {{type}} \ + × \ + \ + \ +
\ +
\ + ', + geo_distance: ' \ +
\ +
\ + \ + {{field}} {{type}} \ + × \ + \ + \ + \ + \ + \ + \ + \ +
\ +
\ + ' + }, events: { 'click .js-remove-filter': 'onRemoveFilter', 'click .js-add-filter': 'onAddFilterShow', @@ -3349,30 +3423,23 @@ my.FilterEditor = Backbone.View.extend({ initialize: function() { this.el = $(this.el); _.bindAll(this, 'render'); + this.model.fields.bind('all', this.render); this.model.queryState.bind('change', this.render); this.model.queryState.bind('change:filters:new-blank', this.render); this.render(); }, render: function() { + var self = this; var tmplData = $.extend(true, {}, this.model.queryState.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] - }; - }); tmplData.fields = this.model.fields.toJSON(); + tmplData.filterRender = function() { + return Mustache.render(self.filterTemplates[this.type], this); + }; var out = Mustache.render(this.template, tmplData); this.el.html(out); }, @@ -3388,9 +3455,7 @@ my.FilterEditor = Backbone.View.extend({ $target.hide(); var filterType = $target.find('select.filterType').val(); var field = $target.find('select.fields').val(); - if (filterType === 'term') { - this.model.queryState.addTermFilter(field); - } + this.model.queryState.addFilter({type: filterType, field: field}); // trigger render explicitly as queryState change will not be triggered (as blank value for filter) this.render(); }, @@ -3407,10 +3472,20 @@ my.FilterEditor = Backbone.View.extend({ 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 filterType = $input.attr('data-filter-type'); var fieldId = $input.attr('data-filter-field'); - filters[filterIndex].term[fieldId] = value; + var filterIndex = parseInt($input.attr('data-filter-id')); + var name = $input.attr('name'); + var value = $input.val(); + if (filterType === 'term') { + filters[filterIndex].term = value; + } else if (filterType === 'geo_distance') { + if (name === 'distance') { + filters[filterIndex].distance = parseFloat(value); + } else { + filters[filterIndex].point[name] = parseFloat(value); + } + } }); self.model.queryState.set({filters: filters}); self.model.queryState.trigger('change'); @@ -4023,44 +4098,57 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; }; this._normalizeQuery = function(queryObj) { - var out = queryObj && queryObj.toJSON ? queryObj.toJSON() : _.extend({}, queryObj); - if (out.q !== undefined && out.q.trim() === '') { - delete out.q; - } - if (!out.q) { - out.query = { + var self = this; + var queryInfo = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj); + var out = { + constant_score: { + query: {} + } + }; + if (!queryInfo.q) { + out.constant_score.query = { match_all: {} }; } else { - out.query = { + out.constant_score.query = { query_string: { - query: out.q + query: queryInfo.q } }; - delete out.q; } - // now do filters (note the *plural*) - if (out.filters && out.filters.length) { - if (!out.filter) { - out.filter = {}; - } - if (!out.filter.and) { - out.filter.and = []; - } - out.filter.and = out.filter.and.concat(out.filters); - } - if (out.filters !== undefined) { - delete out.filters; + if (queryInfo.filters && queryInfo.filters.length) { + out.constant_score.filter = { + and: [] + }; + _.each(queryInfo.filters, function(filter) { + out.constant_score.filter.and.push(self._convertFilter(filter)); + }); } return out; - }; + }, + + this._convertFilter = function(filter) { + var out = {}; + out[filter.type] = {} + if (filter.type === 'term') { + out.term[filter.field] = filter.term.toLowerCase(); + } else if (filter.type === 'geo_distance') { + out.geo_distance[filter.field] = filter.point; + out.geo_distance.distance = filter.distance; + } + return out; + }, // ### query // // @return deferred supporting promise API this.query = function(queryObj) { + var esQuery = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj); var queryNormalized = this._normalizeQuery(queryObj); - var data = {source: JSON.stringify(queryNormalized)}; + delete esQuery.q; + delete esQuery.filters; + esQuery.query = queryNormalized; + var data = {source: JSON.stringify(esQuery)}; var url = this.endpoint + '/_search'; var jqxhr = recline.Backend.makeRequest({ url: url, @@ -4149,6 +4237,12 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; results.hits.facets = results.facets; } dfd.resolve(results.hits); + }).fail(function(errorObj) { + var out = { + title: 'Failed: ' + errorObj.status + ' code', + message: errorObj.responseText + }; + dfd.reject(out); }); return dfd.promise(); }; @@ -4403,8 +4497,11 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; var fieldName = _.keys(sortObj)[0]; results = _.sortBy(results, function(doc) { var _out = doc[fieldName]; - return (sortObj[fieldName].order == 'asc') ? _out : -1*_out; + return _out; }); + if (sortObj[fieldName].order == 'desc') { + results.reverse(); + } }); var total = results.length; var facets = this.computeFacets(results, queryObj); @@ -4419,10 +4516,12 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; // in place filtering this._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]); - }); + // if a term filter ... + if (filter.type === 'term') { + results = _.filter(results, function(doc) { + return (doc[filter.field] == filter.term); + }); + } }); return results; };