[#111,filter/geo][m]: geo filter support - filter editor working though not sure actual query is working (!).

* Extensive refactoring of Model.Query and View.FilterEditor to do this cleanly (geo_distance and term treated similarly now)
This commit is contained in:
Rufus Pollock 2012-06-15 10:51:13 +01:00
parent 952c85a912
commit 8fe04ddd4f
5 changed files with 160 additions and 35 deletions

View File

@ -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;
};

View File

@ -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

View File

@ -15,7 +15,8 @@ my.FilterEditor = Backbone.View.extend({
<fieldset> \
<label>Filter type</label> \
<select class="filterType"> \
<option value="term">Term (text) filter</option> \
<option value="term">Term (text)</option> \
<option value="geo_distance">Geo distance</option> \
</select> \
<label>Field</label> \
<select class="fields"> \
@ -27,21 +28,37 @@ my.FilterEditor = Backbone.View.extend({
</fieldset> \
</form> \
<form class="form-stacked js-edit"> \
{{#termFilters}} \
<div class="control-group filter-term filter" data-filter-id={{id}}> \
<label class="control-label" for="">{{label}}</label> \
<div class="controls"> \
<input type="text" value="{{value}}" name="{{fieldId}}" data-filter-field="{{fieldId}}" data-filter-id="{{id}}" data-filter-type="term" /> \
<a class="js-remove-filter" href="#">&times;</a> \
</div> \
</div> \
{{/termFilters}} \
{{#termFilters.length}} \
{{#filters}} \
{{{filterRender}}} \
{{/filters}} \
{{#filters.length}} \
<button type="submit" class="btn">Update</button> \
{{/termFilters.length}} \
{{/filters.length}} \
</form> \
</div> \
',
filterTemplates: {
term: ' \
<div class="control-group filter-{{_type}} filter"> \
<label class="control-label" for="">{{_field}}</label> \
<div class="controls"> \
<input type="text" value="{{_value}}" name="term" data-filter-field="{{_field}}" data-filter-id="{{id}}" data-filter-type="{{_type}}" /> \
<a class="js-remove-filter" href="#">&times;</a> \
</div> \
</div> \
',
geo_distance: ' \
<div class="control-group filter-{{_type}} filter"> \
<label class="control-label" for="">{{_field}}</label> \
<a class="js-remove-filter" href="#">&times;</a> \
<div class="controls"> \
<input type="text" value="{{_value.lon}}" name="lon" data-filter-field="{{_field}}" data-filter-id="{{id}}" data-filter-type="{{_type}}" /> \
<input type="text" value="{{_value.lat}}" name="lat" data-filter-field="{{_field}}" data-filter-id="{{id}}" data-filter-type="{{_type}}" /> \
<input type="text" value="{{distance}}" name="distance" data-filter-field="{{_field}}" data-filter-id="{{id}}" data-filter-type="{{_type}}" /> \
</div> \
</div> \
'
},
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');

View File

@ -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]);

View File

@ -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();
});