diff --git a/src/backend/memory.js b/src/backend/memory.js index daf78dbf..39c11e9c 100644 --- a/src/backend/memory.js +++ b/src/backend/memory.js @@ -90,10 +90,13 @@ 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.term) { + results = _.filter(results, function(doc) { + var fieldId = _.keys(filter.term)[0]; + return (doc[fieldId] == filter.term[fieldId]); + }); + } }); return results; }; diff --git a/src/model.js b/src/model.js index 2628903e..0d9b3975 100644 --- a/src/model.js +++ b/src/model.js @@ -393,7 +393,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** @@ -417,6 +417,37 @@ my.Query = Backbone.Model.extend({ filters: [] }; }, + _filterTemplates: { + term: { + '{{fieldId}}': '' + }, + geo_distance: { + distance: '10km', + '{{fieldId}}': { + lon: 0, + lat: 0 + } + } + }, + // ### addFilter + // + // Add a new filter (appended to the list of filters) with default + // value. To set value for the filter use updateFilter. + // + // @param type type of this filter e.g. term, geo_distance etc + // @param fieldId the field to add this filter on + addFilter: function(type, fieldId) { + var tmpl = JSON.stringify(this._filterTemplates[type]); + var filters = this.get('filters'); + var filter = {}; + filter[type] = JSON.parse(Mustache.render(tmpl, {type: type, fieldId: fieldId})); + filter[type]._type = type; + filter[type]._field = fieldId; + filters.push(filter); + this.trigger('change:filters:new-blank'); + }, + updateFilter: function(index, value) { + }, // #### addTermFilter // // Set (update or add) a terms filter to filters @@ -436,6 +467,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 diff --git a/src/widget.filtereditor.js b/src/widget.filtereditor.js index a53a8b69..56962891 100644 --- a/src/widget.filtereditor.js +++ b/src/widget.filtereditor.js @@ -15,7 +15,8 @@ my.FilterEditor = Backbone.View.extend({
\ \ \ \ \ - × \ - \ - \ - {{/termFilters}} \ - {{#termFilters.length}} \ + {{#filters}} \ + {{{filterRender}}} \ + {{/filters}} \ + {{#filters.length}} \ \ - {{/termFilters.length}} \ + {{/filters.length}} \ \ \ ', + filterTemplates: { + term: ' \ +
\ + \ +
\ + \ + × \ +
\ +
\ + ', + geo_distance: ' \ +
\ + \ + × \ +
\ + \ + \ + \ +
\ +
\ + ' + }, events: { 'click .js-remove-filter': 'onRemoveFilter', 'click .js-add-filter': 'onAddFilterShow', @@ -51,30 +68,28 @@ 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() { + var filterType = _.keys(this)[0]; + var _data = this[filterType]; + _data.id = this.id; + _data._type = filterType; + _data._value = _data[_data._field]; + return Mustache.render(self.filterTemplates[filterType], _data); + }; var out = Mustache.render(this.template, tmplData); this.el.html(out); }, @@ -90,9 +105,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(filterType, field); // trigger render explicitly as queryState change will not be triggered (as blank value for filter) this.render(); }, @@ -109,10 +122,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[fieldId] = value; + } else if (filterType === 'geo_distance') { + if (name === 'distance') { + filters[filterIndex].geo_distance.distance = parseInt(value); + } else { + filters[filterIndex].geo_distance[fieldId][name] = parseFloat(value); + } + } }); self.model.queryState.set({filters: filters}); self.model.queryState.trigger('change'); diff --git a/test/model.test.js b/test/model.test.js index 0433be74..05508888 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -150,6 +150,33 @@ test('Query', function () { }); test('Query.addFilter', function () { + var query = new recline.Model.Query(); + query.addFilter('term', 'xyz'); + var exp = { + term: { + xyz: '', + _field: 'xyz', + _type: 'term' + } + }; + deepEqual(exp, query.get('filters')[0]); + + query.addFilter('geo_distance', 'xyz'); + var exp = { + geo_distance: { + distance: '10km', + xyz: { + lon: 0, + lat: 0 + }, + _field: 'xyz', + _type: 'geo_distance' + } + }; + deepEqual(exp, query.get('filters')[1]); +}); + +test('Query.addTermFilter', function () { var query = new recline.Model.Query(); query.addTermFilter('xyz', 'this-value'); deepEqual({term: {xyz: 'this-value'}}, query.get('filters')[0]); diff --git a/test/widget.filtereditor.test.js b/test/widget.filtereditor.test.js index 627db158..f991918a 100644 --- a/test/widget.filtereditor.test.js +++ b/test/widget.filtereditor.test.js @@ -39,3 +39,28 @@ test('basics', function () { view.remove(); }); +test('geo_distance', function () { + var dataset = Fixture.getDataset(); + var view = new recline.View.FilterEditor({ + model: dataset + }); + $('.fixtures').append(view.el); + + var $addForm = view.el.find('form.js-add'); + // submit the form + $addForm.find('select.filterType').val('geo_distance'); + $addForm.find('select.fields').val('lon'); + $addForm.submit(); + + // now check we have new filter + $editForm = view.el.find('form.js-edit'); + equal($editForm.find('.filter-geo_distance').length, 1) + deepEqual(_.keys(dataset.queryState.attributes.filters[0].geo_distance), ['distance', 'lon', '_type', '_field']); + + // now set filter value and apply + $editForm.find('input[name="lat"]').val(10); + $editForm.submit(); + equal(dataset.queryState.attributes.filters[0].geo_distance.lon.lat, 10); + + view.remove(); +});