[WIP] VS-less search view: remove visualsearch, start building coherent model backing to the view
bzr revid: xmo@openerp.com-20120424105941-x8dvot5mjruvxcho
This commit is contained in:
parent
2ccb3e793f
commit
9046ab46fa
|
@ -35,19 +35,6 @@
|
|||
"static/lib/underscore/underscore.string.js",
|
||||
"static/lib/backbone/backbone.js",
|
||||
|
||||
"static/lib/visualsearch/lib/js/visualsearch.js",
|
||||
"static/lib/visualsearch/lib/js/utils/backbone_extensions.js",
|
||||
"static/lib/visualsearch/lib/js/utils/hotkeys.js",
|
||||
"static/lib/visualsearch/lib/js/utils/inflector.js",
|
||||
"static/lib/visualsearch/lib/js/utils/jquery_extensions.js",
|
||||
"static/lib/visualsearch/lib/js/utils/search_parser.js",
|
||||
"static/lib/visualsearch/lib/js/models/search_facets.js",
|
||||
"static/lib/visualsearch/lib/js/models/search_query.js",
|
||||
"static/lib/visualsearch/lib/js/templates/templates.js",
|
||||
"static/lib/visualsearch/lib/js/views/search_facet.js",
|
||||
"static/lib/visualsearch/lib/js/views/search_input.js",
|
||||
"static/lib/visualsearch/lib/js/views/search_box.js",
|
||||
|
||||
"static/lib/labjs/LAB.src.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/src/js/boot.js",
|
||||
|
@ -73,9 +60,6 @@
|
|||
"static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
|
||||
"static/lib/jquery.ui.notify/css/ui.notify.css",
|
||||
"static/lib/jquery.tipsy/tipsy.css",
|
||||
"static/lib/visualsearch/lib/css/reset.css",
|
||||
"static/lib/visualsearch/lib/css/workspace.css",
|
||||
"static/lib/visualsearch/lib/css/icons.css",
|
||||
# "static/src/css/base_old.css",
|
||||
"static/src/css/base.css",
|
||||
"static/src/css/data_export.css",
|
||||
|
|
|
@ -1048,6 +1048,8 @@
|
|||
.openerp .oe_searchview {
|
||||
position: relative;
|
||||
float: right;
|
||||
padding-right: 20px;
|
||||
background: white;
|
||||
}
|
||||
.openerp .oe_searchview .VS-search .VS-search-box {
|
||||
min-height: 0;
|
||||
|
@ -1133,7 +1135,7 @@
|
|||
.openerp .oe_searchview .VS-search .VS-icon-cancel {
|
||||
right: 24px;
|
||||
}
|
||||
.openerp .oe_searchview .VS-search .oe_vs_unfold_drawer {
|
||||
.openerp .oe_searchview .oe_searchview_unfold_drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
@ -1143,7 +1145,7 @@
|
|||
color: #cccccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
.openerp .oe_searchview .VS-search .oe_vs_unfold_drawer:before {
|
||||
.openerp .oe_searchview .oe_searchview_unfold_drawer:before {
|
||||
content: "▾";
|
||||
}
|
||||
.openerp .oe_searchview.oe_searchview_open_drawer .oe_searchview_drawer {
|
||||
|
|
|
@ -821,6 +821,8 @@ $colour4: #8a89ba
|
|||
.oe_searchview
|
||||
position: relative
|
||||
float: right
|
||||
padding-right: 20px
|
||||
background: white
|
||||
.VS-search
|
||||
.VS-search-box
|
||||
min-height: 0
|
||||
|
@ -908,17 +910,17 @@ $colour4: #8a89ba
|
|||
.VS-icon-cancel
|
||||
right: 24px
|
||||
|
||||
.oe_vs_unfold_drawer
|
||||
position: absolute
|
||||
top: 0
|
||||
right: 0
|
||||
height: 100%
|
||||
line-height: 23px
|
||||
padding: 0 7px 0 4px
|
||||
color: #ccc
|
||||
cursor: pointer
|
||||
&:before
|
||||
content: "▾"
|
||||
.oe_searchview_unfold_drawer
|
||||
position: absolute
|
||||
top: 0
|
||||
right: 0
|
||||
height: 100%
|
||||
line-height: 23px
|
||||
padding: 0 7px 0 4px
|
||||
color: #ccc
|
||||
cursor: pointer
|
||||
&:before
|
||||
content: "▾"
|
||||
|
||||
&.oe_searchview_open_drawer
|
||||
.oe_searchview_drawer
|
||||
|
|
|
@ -6,67 +6,143 @@ _.mixin({
|
|||
sum: function (obj) { return _.reduce(obj, function (a, b) { return a + b; }, 0); }
|
||||
});
|
||||
|
||||
// Have SearchBox optionally use callback function to produce inputs and facets
|
||||
// (views) set on callbacks.make_facet and callbacks.make_input keys when
|
||||
// initializing VisualSearch
|
||||
var SearchBox_renderFacet = function (facet, position) {
|
||||
var view = new (this.app.options.callbacks['make_facet'] || VS.ui.SearchFacet)({
|
||||
app : this.app,
|
||||
model : facet,
|
||||
order : position
|
||||
});
|
||||
/** @namespace */
|
||||
var my = instance.web.search = {};
|
||||
|
||||
// Input first, facet second.
|
||||
this.renderSearchInput();
|
||||
this.facetViews.push(view);
|
||||
this.$('.VS-search-inner').children().eq(position*2).after(view.render().el);
|
||||
my.FacetValue = Backbone.Model.extend({
|
||||
|
||||
view.calculateSize();
|
||||
_.defer(_.bind(view.calculateSize, view));
|
||||
|
||||
return view;
|
||||
}; // warning: will not match
|
||||
// Ensure we're replacing the function we think
|
||||
if (SearchBox_renderFacet.toString() !== VS.ui.SearchBox.prototype.renderFacet.toString().replace(/(VS\.ui\.SearchFacet)/, "(this.app.options.callbacks['make_facet'] || $1)")) {
|
||||
throw new Error(
|
||||
"Trying to replace wrong version of VS.ui.SearchBox#renderFacet. "
|
||||
+ "Please fix replacement.");
|
||||
}
|
||||
var SearchBox_renderSearchInput = function () {
|
||||
var input = new (this.app.options.callbacks['make_input'] || VS.ui.SearchInput)({position: this.inputViews.length, app: this.app});
|
||||
this.$('.VS-search-inner').append(input.render().el);
|
||||
this.inputViews.push(input);
|
||||
};
|
||||
// Ensure we're replacing the function we think
|
||||
if (SearchBox_renderSearchInput.toString() !== VS.ui.SearchBox.prototype.renderSearchInput.toString().replace(/(VS\.ui\.SearchInput)/, "(this.app.options.callbacks['make_input'] || $1)")) {
|
||||
throw new Error(
|
||||
"Trying to replace wrong version of VS.ui.SearchBox#renderSearchInput. "
|
||||
+ "Please fix replacement.");
|
||||
}
|
||||
var SearchBox_searchEvent = function (e) {
|
||||
var query = null;
|
||||
this.renderFacets();
|
||||
this.focusSearch(e);
|
||||
this.app.options.callbacks.search(query, this.app.searchQuery);
|
||||
};
|
||||
if (SearchBox_searchEvent.toString() !== VS.ui.SearchBox.prototype.searchEvent.toString().replace(
|
||||
/this\.value\(\);\n[ ]{4}this\.focusSearch\(e\);\n[ ]{4}this\.value\(query\)/,
|
||||
'null;\n this.renderFacets();\n this.focusSearch(e)')) {
|
||||
throw new Error(
|
||||
"Trying to replace wrong version of VS.ui.SearchBox#searchEvent. "
|
||||
+ "Please fix replacement.");
|
||||
}
|
||||
_.extend(VS.ui.SearchBox.prototype, {
|
||||
renderFacet: SearchBox_renderFacet,
|
||||
renderSearchInput: SearchBox_renderSearchInput,
|
||||
searchEvent: SearchBox_searchEvent
|
||||
});
|
||||
_.extend(VS.model.SearchFacet.prototype, {
|
||||
value: function () {
|
||||
if (this.has('json')) {
|
||||
return this.get('json');
|
||||
my.FacetValues = Backbone.Collection.extend({
|
||||
model: my.FacetValue
|
||||
});
|
||||
my.Facet = Backbone.Model.extend({
|
||||
initialize: function (attrs) {
|
||||
var values = attrs.values;
|
||||
delete attrs.values;
|
||||
|
||||
Backbone.Model.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.values = new my.FacetValues(values || []);
|
||||
this.values.on('add remove change reset', function () {
|
||||
this.trigger('change', this);
|
||||
}, this);
|
||||
},
|
||||
get: function (key) {
|
||||
if (key !== 'values') {
|
||||
return Backbone.Model.prototype.get.call(this, key);
|
||||
}
|
||||
return this.get('value');
|
||||
return this.values.toJSON();
|
||||
},
|
||||
set: function (key, value) {
|
||||
if (key !== 'values') {
|
||||
return Backbone.Model.prototype.set.call(this, key, value);
|
||||
}
|
||||
this.values.reset(value);
|
||||
}
|
||||
});
|
||||
my.SearchQuery = Backbone.Collection.extend({
|
||||
model: my.Facet,
|
||||
initialize: function () {
|
||||
Backbone.Collection.prototype.initialize.apply(
|
||||
this, arguments);
|
||||
this.on('change', function (facet) {
|
||||
if(!facet.values.isEmpty()) { return; }
|
||||
|
||||
this.remove(facet);
|
||||
}, this);
|
||||
},
|
||||
add: function (value, options) {
|
||||
options || (options = {});
|
||||
if (value instanceof Array) {
|
||||
throw new Error("Can't add multiple facets to a query at once");
|
||||
}
|
||||
var model = this._prepareModel(value, options);
|
||||
var previous = this.detect(function (facet) {
|
||||
return facet.get('category') === model.get('category')
|
||||
&& facet.get('field') === model.get('field');
|
||||
});
|
||||
if (previous) {
|
||||
previous.values.add(model.get('values'));
|
||||
return this;
|
||||
}
|
||||
return Backbone.Collection.prototype.add.call(this, model, options);
|
||||
},
|
||||
toggle: function (value, options) {
|
||||
options || (options = {});
|
||||
|
||||
var facet = this.detect(function (facet) {
|
||||
return facet.get('category') === value.category
|
||||
&& facet.get('field') === value.field;
|
||||
});
|
||||
if (!facet) {
|
||||
return this.add(value, options);
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
_(value.values).each(function (val) {
|
||||
var already_value = facet.values.detect(function (v) {
|
||||
return v.get('value') === val.value
|
||||
&& v.get('label') === val.label;
|
||||
});
|
||||
// toggle value
|
||||
if (already_value) {
|
||||
facet.values.remove(already_value, {silent: true});
|
||||
} else {
|
||||
facet.values.add(val, {silent: true});
|
||||
}
|
||||
changed = true;
|
||||
});
|
||||
// "Commit" changes to values array as a single call, so observers of
|
||||
// change event don't get misled by intermediate incomplete toggling
|
||||
// states
|
||||
facet.trigger('change', facet);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
my.InputView = instance.web.Widget.extend({
|
||||
template: 'SearchView.InputView'
|
||||
});
|
||||
my.FacetView = instance.web.Widget.extend({
|
||||
template: 'SearchView.FacetView',
|
||||
init: function (parent, model) {
|
||||
this._super(parent);
|
||||
this.model = model;
|
||||
this.model.on('change', this.model_changed, this);
|
||||
},
|
||||
destroy: function () {
|
||||
this.model.off('change', this.model_changed, this);
|
||||
this._super();
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
var $e = self.$element.find('span');
|
||||
var q = $.when(this._super());
|
||||
return q.pipe(function () {
|
||||
var values = self.model.values.map(function (value) {
|
||||
return new my.FacetValueView(self, value).appendTo($e);
|
||||
});
|
||||
|
||||
return $.when.apply(null, values);
|
||||
});
|
||||
},
|
||||
model_changed: function () {
|
||||
this.$element.text(this.$element.text() + '*');
|
||||
}
|
||||
});
|
||||
my.FacetValueView = instance.web.Widget.extend({
|
||||
template: 'SearchView.FacetView.Value',
|
||||
init: function (parent, model) {
|
||||
this._super(parent);
|
||||
this.model = model;
|
||||
this.model.on('change', this.model_changed, this);
|
||||
},
|
||||
destroy: function () {
|
||||
this.model.off('change', this.model_changed, this);
|
||||
this._super();
|
||||
},
|
||||
model_changed: function () {
|
||||
this.$element.text(this.$element.text() + '*');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -74,13 +150,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
template: "SearchView",
|
||||
/**
|
||||
* @constructs instance.web.SearchView
|
||||
* @extends instance.web.OldWidget
|
||||
* @extends instance.web.Widget
|
||||
*
|
||||
* @param parent
|
||||
* @param element_id
|
||||
* @param dataset
|
||||
* @param view_id
|
||||
* @param defaults
|
||||
* @param hidden
|
||||
*/
|
||||
init: function(parent, dataset, view_id, defaults, hidden) {
|
||||
this._super(parent);
|
||||
|
@ -99,6 +175,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
|
||||
this.filter_data = {};
|
||||
|
||||
this.input_subviews = [];
|
||||
|
||||
this.ready = $.Deferred();
|
||||
},
|
||||
start: function() {
|
||||
|
@ -106,34 +184,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
var p = this._super();
|
||||
|
||||
this.setup_global_completion();
|
||||
this.vs = VS.init({
|
||||
container: this.$element,
|
||||
query: '',
|
||||
callbacks: {
|
||||
make_facet: this.proxy('make_visualsearch_facet'),
|
||||
make_input: this.proxy('make_visualsearch_input'),
|
||||
search: function (query, searchCollection) {
|
||||
self.do_search();
|
||||
},
|
||||
facetMatches: function (callback) {
|
||||
},
|
||||
valueMatches : function(facet, searchTerm, callback) {
|
||||
}
|
||||
this.query = new my.SearchQuery()
|
||||
.on('add change reset', this.proxy('do_search'))
|
||||
.on('add change reset', this.proxy('renderFacets'))
|
||||
.on('remove', function (record, collection, options) {
|
||||
self.renderFacets();
|
||||
if (options.trigger_search) {
|
||||
self.do_search();
|
||||
}
|
||||
});
|
||||
|
||||
var search = function () { self.vs.searchBox.searchEvent({}); };
|
||||
// searchQuery operations
|
||||
this.vs.searchQuery
|
||||
.off('add').on('add', search)
|
||||
.off('change').on('change', search)
|
||||
.off('reset').on('reset', search)
|
||||
.off('remove').on('remove', function (record, collection, options) {
|
||||
if (options['trigger_search']) {
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.hidden) {
|
||||
this.$element.hide();
|
||||
}
|
||||
|
@ -154,7 +214,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
.then(this.on_loaded);
|
||||
}
|
||||
|
||||
this.$element.on('click', '.oe_vs_unfold_drawer', function () {
|
||||
this.$element.on('click', '.oe_searchview_unfold_drawer', function () {
|
||||
self.$element.toggleClass('oe_searchview_open_drawer');
|
||||
});
|
||||
|
||||
|
@ -172,7 +232,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
*/
|
||||
setup_stuff_drawer: function () {
|
||||
var self = this;
|
||||
$('<div class="oe_vs_unfold_drawer">').appendTo(this.$element.find('.VS-search-box'));
|
||||
$('<div class="oe_searchview_unfold_drawer">').appendTo(this.$element);
|
||||
var $drawer = $('<div class="oe_searchview_drawer">').appendTo(this.$element);
|
||||
var $filters = $('<div class="oe_searchview_filters">').appendTo($drawer);
|
||||
|
||||
|
@ -301,50 +361,28 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
*/
|
||||
select_completion: function (e, ui) {
|
||||
e.preventDefault();
|
||||
this.vs.searchQuery.add(new VS.model.SearchFacet(_.extend(
|
||||
{app: this.vs}, ui.item)));
|
||||
this.vs.searchBox.searchEvent({});
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds the right SearchFacet view based on the facet object to render
|
||||
* (e.g. readonly facets for filters)
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {VS.model.SearchFacet} options.model facet object to render
|
||||
*/
|
||||
make_visualsearch_facet: function (options) {
|
||||
return new instance.web.search.FilterGroupFacet(options);
|
||||
|
||||
// if (options.model.get('field') instanceof instance.web.search.FilterGroup) {
|
||||
// return new instance.web.search.FilterGroupFacet(options);
|
||||
// }
|
||||
// return new VS.ui.SearchFacet(options);
|
||||
// FIXME: could have multiple values, shitty API
|
||||
this.query.add_value(ui.item.category, ui.item.values[0]);
|
||||
},
|
||||
/**
|
||||
* Proxies searches on a SearchInput to the search view's global completion
|
||||
*
|
||||
* Also disables SearchInput.autocomplete#_move so search view's
|
||||
* autocomplete can get the corresponding events, or something.
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
make_visualsearch_input: function (options) {
|
||||
var self = this, input = new VS.ui.SearchInput(options);
|
||||
input.setupAutocomplete = function () {
|
||||
_.extend(this.box.autocomplete({
|
||||
minLength: 1,
|
||||
delay: 0,
|
||||
search: function () {
|
||||
self.$element.autocomplete('search', input.box.val());
|
||||
return false;
|
||||
}
|
||||
}).data('autocomplete'), {
|
||||
_move: function () {},
|
||||
close: function () { self.$element.autocomplete('close'); }
|
||||
});
|
||||
};
|
||||
return input;
|
||||
renderFacets: function () {
|
||||
var self = this;
|
||||
var $e = this.$element.find('div.oe_searchview_facets');
|
||||
_.invoke(this.input_subviews, 'destroy');
|
||||
this.input_subviews = [];
|
||||
|
||||
var i = new my.InputView(this);
|
||||
i.appendTo($e);
|
||||
this.input_subviews.push(i);
|
||||
this.query.each(function (facet) {
|
||||
var f = new my.FacetView(this, facet);
|
||||
f.appendTo($e);
|
||||
self.input_subviews.push(f);
|
||||
|
||||
var i = new my.InputView(this);
|
||||
i.appendTo($e);
|
||||
self.input_subviews.push(i);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -428,8 +466,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
this.setup_stuff_drawer(),
|
||||
$.when.apply(null, _(this.inputs).invoke('facet_for_defaults', this.defaults))
|
||||
.then(function () {
|
||||
self.vs.searchQuery.reset(_(arguments).compact(), {silent: true});
|
||||
self.vs.searchBox.renderFacets();
|
||||
self.query.reset(_(arguments).compact(), {silent: true});
|
||||
self.renderFacets();
|
||||
}))
|
||||
.then(function () { self.ready.resolve(); })
|
||||
},
|
||||
|
@ -583,7 +621,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
do_search: function () {
|
||||
var domains = [], contexts = [], groupbys = [], errors = [];
|
||||
|
||||
this.vs.searchQuery.each(function (facet) {
|
||||
return this.on_search([], [], []);
|
||||
this.query.each(function (facet) {
|
||||
var field = facet.get('field');
|
||||
try {
|
||||
var domain = field.get_domain(facet);
|
||||
|
@ -647,40 +686,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
}
|
||||
});
|
||||
|
||||
/** @namespace */
|
||||
instance.web.search = {};
|
||||
|
||||
instance.web.search.FilterGroupFacet = VS.ui.SearchFacet.extend({
|
||||
events: _.extend({
|
||||
'click': 'selectFacet'
|
||||
}, VS.ui.SearchFacet.prototype.events),
|
||||
|
||||
render: function () {
|
||||
this.setMode('not', 'editing');
|
||||
this.setMode('not', 'selected');
|
||||
|
||||
var value = this.model.get('value');
|
||||
this.$el.html(QWeb.render('SearchView.filters.facet', {
|
||||
facet: this.model
|
||||
}));
|
||||
// virtual input so SearchFacet code has something to play with
|
||||
this.box = $('<input>').val(value);
|
||||
|
||||
return this;
|
||||
},
|
||||
enableEdit: function () {
|
||||
this.selectFacet()
|
||||
},
|
||||
keydown: function (e) {
|
||||
var key = VS.app.hotkeys.key(e);
|
||||
if (key !== 'right') {
|
||||
return VS.ui.SearchFacet.prototype.keydown.call(this, e);
|
||||
}
|
||||
e.preventDefault();
|
||||
this.deselectFacet();
|
||||
this.options.app.searchBox.focusNextFacet(this, 1);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Registry of search fields, called by :js:class:`instance.web.SearchView` to
|
||||
* find and instantiate its field widgets.
|
||||
|
@ -780,8 +785,8 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan
|
|||
return $.when(null)
|
||||
},
|
||||
/**
|
||||
* Returns a VS.model.SearchFacet instance for the provided defaults if
|
||||
* they apply to this widget, or null if they don't.
|
||||
* Returns a Facet instance for the provided defaults if they apply to
|
||||
* this widget, or null if they don't.
|
||||
*
|
||||
* This default implementation will try calling
|
||||
* :js:func:`instance.web.search.Input#facet_for` if the widget's name
|
||||
|
@ -845,14 +850,11 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
return f.attrs && f.attrs.name && !!defaults[f.attrs.name];
|
||||
});
|
||||
if (_.isEmpty(fs)) { return $.when(null); }
|
||||
return $.when(new VS.model.SearchFacet({
|
||||
return $.when({
|
||||
category: _t("Filter"),
|
||||
value: _(fs).map(function (f) {
|
||||
return f.attrs.string || f.attrs.name }).join(' | '),
|
||||
json: fs,
|
||||
field: this,
|
||||
app: this.view.vs
|
||||
}));
|
||||
values: fs,
|
||||
field: this
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Fetches contexts for all enabled filters in the group
|
||||
|
@ -861,7 +863,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
* @return {*} combined contexts of the enabled filters in this group
|
||||
*/
|
||||
get_context: function (facet) {
|
||||
var contexts = _(facet.get('json')).chain()
|
||||
var contexts = _(facet.get('values')).chain()
|
||||
.map(function (filter) { return filter.attrs.context; })
|
||||
.reject(_.isEmpty)
|
||||
.value();
|
||||
|
@ -879,7 +881,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
* @return {Array} enabled filters in this group
|
||||
*/
|
||||
get_groupby: function (facet) {
|
||||
return _(facet.get('json')).chain()
|
||||
return _(facet.get('values')).chain()
|
||||
.map(function (filter) { return filter.attrs.context; })
|
||||
.reject(_.isEmpty)
|
||||
.value();
|
||||
|
@ -891,7 +893,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
* @return {*} combined domains of the enabled filters in this group
|
||||
*/
|
||||
get_domain: function (facet) {
|
||||
var domains = _(facet.get('json')).chain()
|
||||
var domains = _(facet.get('values')).chain()
|
||||
.map(function (filter) { return filter.attrs.domain; })
|
||||
.reject(_.isEmpty)
|
||||
.value();
|
||||
|
@ -911,10 +913,10 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
toggle: function (filter) {
|
||||
// FIXME: oh god, my eyes, they hurt
|
||||
var self = this, fs;
|
||||
var facet = this.view.vs.searchQuery.detect(function (f) {
|
||||
var facet = this.view.query.detect(function (f) {
|
||||
return f.get('field') === self; });
|
||||
if (facet) {
|
||||
fs = facet.get('json');
|
||||
fs = facet.get('values');
|
||||
|
||||
if (_.include(fs, filter)) {
|
||||
fs = _.without(fs, filter);
|
||||
|
@ -922,12 +924,10 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
fs.push(filter);
|
||||
}
|
||||
if (_(fs).isEmpty()) {
|
||||
this.view.vs.searchQuery.remove(facet, {trigger_search: true});
|
||||
this.view.query.remove(facet, {trigger_search: true});
|
||||
} else {
|
||||
facet.set({
|
||||
json: fs,
|
||||
value: _(fs).map(function (f) {
|
||||
return f.attrs.string || f.attrs.name }).join(' | ')
|
||||
values: fs
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
@ -935,13 +935,10 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
|
|||
fs = [filter];
|
||||
}
|
||||
|
||||
this.view.vs.searchQuery.add({
|
||||
this.view.query.add({
|
||||
category: _t("Filter"),
|
||||
value: _(fs).map(function (f) {
|
||||
return f.attrs.string || f.attrs.name }).join(' | '),
|
||||
json: fs,
|
||||
field: this,
|
||||
app: this.view.vs
|
||||
values: fs,
|
||||
field: this
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -985,13 +982,10 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
|
|||
this.load_attrs(_.extend({}, field, view_section.attrs));
|
||||
},
|
||||
facet_for: function (value) {
|
||||
return $.when(new VS.model.SearchFacet({
|
||||
return $.when({
|
||||
category: this.attrs.string || this.attrs.name,
|
||||
value: String(value),
|
||||
json: value,
|
||||
field: this,
|
||||
app: this.view.vs
|
||||
}));
|
||||
values: [{label: String(value), value: value}]
|
||||
});
|
||||
},
|
||||
get_value: function (facet) {
|
||||
return facet.value();
|
||||
|
@ -1061,8 +1055,7 @@ instance.web.search.CharField = instance.web.search.Field.extend( /** @lends ins
|
|||
return $.when([{
|
||||
category: this.attrs.string,
|
||||
label: label,
|
||||
value: value,
|
||||
field: this
|
||||
value: [{label: label, value: value}]
|
||||
}]);
|
||||
}
|
||||
});
|
||||
|
@ -1142,11 +1135,11 @@ instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends
|
|||
return label.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
|
||||
})
|
||||
.map(function (sel) {
|
||||
// FIXME fucking repetition man, find something better
|
||||
return {
|
||||
category: self.attrs.string,
|
||||
field: self,
|
||||
value: sel[1],
|
||||
json: sel[0]
|
||||
values: [{value: sel[0], label: sel[1]}]
|
||||
};
|
||||
}).value();
|
||||
if (_.isEmpty(results)) { return $.when(null); }
|
||||
|
@ -1159,16 +1152,14 @@ instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends
|
|||
return sel[0] === value;
|
||||
});
|
||||
if (!match) { return $.when(null); }
|
||||
return $.when(new VS.model.SearchFacet({
|
||||
return $.when({
|
||||
category: this.attrs.string,
|
||||
value: match[1],
|
||||
json: match[0],
|
||||
field: this,
|
||||
app: this.view.app
|
||||
}));
|
||||
values: [{label: match[1], value: match[0]}]
|
||||
});
|
||||
},
|
||||
get_value: function (facet) {
|
||||
return facet.get('json');
|
||||
return facet.get('values');
|
||||
}
|
||||
});
|
||||
instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/** @lends instance.web.search.BooleanField# */{
|
||||
|
@ -1197,7 +1188,7 @@ instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/**
|
|||
*/
|
||||
instance.web.search.DateField = instance.web.search.Field.extend(/** @lends instance.web.search.DateField# */{
|
||||
get_value: function (facet) {
|
||||
return instance.web.date_to_str(facet.get('json'));
|
||||
return openerp.web.date_to_str(facet.get('values'));
|
||||
},
|
||||
complete: function (needle) {
|
||||
var d = Date.parse(needle);
|
||||
|
@ -1210,9 +1201,8 @@ instance.web.search.DateField = instance.web.search.Field.extend(/** @lends inst
|
|||
return $.when([{
|
||||
category: this.attrs.string,
|
||||
label: label,
|
||||
value: value,
|
||||
json: d,
|
||||
field: this
|
||||
// FIXME: brain broken
|
||||
values: [{label: value, value: d}]
|
||||
}]);
|
||||
}
|
||||
});
|
||||
|
@ -1229,7 +1219,7 @@ instance.web.search.DateField = instance.web.search.Field.extend(/** @lends inst
|
|||
*/
|
||||
instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @lends instance.web.search.DateTimeField# */{
|
||||
get_value: function (facet) {
|
||||
return instance.web.datetime_to_str(facet.get('json'));
|
||||
return openerp.web.datetime_to_str(facet.get('values'));
|
||||
}
|
||||
});
|
||||
instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
|
||||
|
@ -1252,8 +1242,7 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
|
|||
return {
|
||||
category: self.attrs.string,
|
||||
value: result[1],
|
||||
json: result[0],
|
||||
field: self
|
||||
values: [{label: result[1], value: result[0]}]
|
||||
};
|
||||
}));
|
||||
});
|
||||
|
@ -1261,28 +1250,24 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
|
|||
facet_for: function (value) {
|
||||
var self = this;
|
||||
if (value instanceof Array) {
|
||||
return $.when(new VS.model.SearchFacet({
|
||||
return $.when({
|
||||
category: this.attrs.string,
|
||||
value: value[1],
|
||||
json: value[0],
|
||||
field: this,
|
||||
app: this.view.vs
|
||||
}));
|
||||
values: [{label: value[1], value: value[0]}]
|
||||
});
|
||||
}
|
||||
return this.model.call('name_get', [value], {}).pipe(function (names) {
|
||||
return new VS.model.SearchFacet({
|
||||
return {
|
||||
category: self.attrs.string,
|
||||
value: names[0][1],
|
||||
json: names[0][0],
|
||||
field: self,
|
||||
app: self.view.vs
|
||||
});
|
||||
values: [{label: names[0][1], value: names[0][0]}]
|
||||
};
|
||||
})
|
||||
},
|
||||
make_domain: function (name, operator, facet) {
|
||||
// ``json`` -> actual auto-completed id
|
||||
if (facet.get('json')) {
|
||||
return [[name, '=', facet.get('json')]];
|
||||
if (facet.get('values')) {
|
||||
return [[name, '=', facet.get('values')]];
|
||||
}
|
||||
|
||||
return this._super(name, operator, facet);
|
||||
|
@ -1332,13 +1317,10 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
|
|||
// Create Filter (& FilterGroup around it) with that domain
|
||||
var f = new instance.web.search.FilterGroup(filters, this.view);
|
||||
// add group to query
|
||||
this.view.vs.searchQuery.add({
|
||||
this.query.add({
|
||||
category: _t("Advanced"),
|
||||
value: _(filters).map(function (f) {
|
||||
return f.attrs.string || f.attrs.name }).join(' | '),
|
||||
json: filters,
|
||||
field: f,
|
||||
app: this.view.vs
|
||||
values: filters,
|
||||
field: f
|
||||
});
|
||||
// remove all propositions
|
||||
_.invoke(children, 'destroy');
|
||||
|
|
|
@ -1290,7 +1290,22 @@
|
|||
</t>
|
||||
|
||||
<div t-name="SearchView" class="oe_searchview">
|
||||
<div class="oe_searchview_facets"/>
|
||||
</div>
|
||||
|
||||
<div t-name="SearchView.InputView"
|
||||
class="oe_searchview_input"
|
||||
style="height: 2em; min-width: 1em; display: inline-block;"
|
||||
contenteditable="true"/>
|
||||
<div t-name="SearchView.FacetView"
|
||||
class="oe_searchview_facet"
|
||||
style="outline: 1px solid black; min-width: 40px; height: 2em; display: inline-block">
|
||||
<t t-esc="widget.model.get('category')"/>: <span/>
|
||||
</div>
|
||||
<span t-name="SearchView.FacetView.Value">
|
||||
<t t-esc="widget.model.get('label')"/>
|
||||
</span>
|
||||
|
||||
<t t-name="SearchView.managed-filters">
|
||||
<option class="oe-filters-title" value="">Filters</option>
|
||||
<optgroup label="-- Filters --">
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
$(document).ready(function () {
|
||||
var instance;
|
||||
module('query', {
|
||||
setup: function () {
|
||||
instance = window.openerp.init([]);
|
||||
window.openerp.web.corelib(instance);
|
||||
window.openerp.web.coresetup(instance);
|
||||
window.openerp.web.chrome(instance);
|
||||
window.openerp.web.data(instance);
|
||||
window.openerp.web.search(instance);
|
||||
}
|
||||
});
|
||||
test('Adding a facet to the query creates a facet and a value', function () {
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
var field = {};
|
||||
query.add({
|
||||
category: 'Foo',
|
||||
field: field,
|
||||
values: [{label: 'Value', value: 3}]
|
||||
});
|
||||
|
||||
var facet = query.at(0);
|
||||
equal(facet.get('category'), 'Foo');
|
||||
equal(facet.get('field'), field);
|
||||
deepEqual(facet.get('values'), [{label: 'Value', value: 3}]);
|
||||
});
|
||||
test('Adding an array of facet is not valid', function () {
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
|
||||
raises(function () {
|
||||
query.add([
|
||||
{ category: 'Foo', field: {}, values: [{label: 'Value', value: 3}] },
|
||||
{ category: 'Bar', field: {}, values: [{label: 'Value 2', value: 4}] }
|
||||
]);
|
||||
});
|
||||
});
|
||||
test('If a facet already exists, add values to it', function () {
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
var field = {};
|
||||
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
query.add({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
|
||||
|
||||
equal(query.length, 1, "adding an existing facet should merge new values into old facet");
|
||||
var facet = query.at(0);
|
||||
deepEqual(facet.get('values'), [
|
||||
{label: 'V1', value: 0},
|
||||
{label: 'V2', value: 1}
|
||||
]);
|
||||
});
|
||||
test('Facet being implicitly changed should trigger change, not add', function () {
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
var field = {}, added = false, changed = false;
|
||||
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
query.on('add', function () { added = true; })
|
||||
.on('change', function () { changed = true });
|
||||
query.add({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
|
||||
|
||||
ok(!added, "query.add adding values to a facet should not trigger an add");
|
||||
ok(changed, "query.add adding values to a facet should not trigger a change");
|
||||
});
|
||||
test('Toggling a facet, value which does not exist should add it', function () {
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
var field = {};
|
||||
query.toggle({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
|
||||
equal(query.length, 1, "Should have created a single facet");
|
||||
var facet = query.at(0);
|
||||
equal(facet.values.length, 1, "Facet should have a single value");
|
||||
deepEqual(facet.get('values'), [{label: 'V1', value: 0}],
|
||||
"Facet's value should match input");
|
||||
});
|
||||
test('Toggling a facet which exists with a value which does not should add the value to the facet', function () {
|
||||
var field = {};
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
query.toggle({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
|
||||
|
||||
equal(query.length, 1, "Should have edited the existing facet");
|
||||
var facet = query.at(0);
|
||||
equal(facet.values.length, 2, "Should have added the value to the existing facet");
|
||||
deepEqual(facet.get('values'), [
|
||||
{label: 'V1', value: 0},
|
||||
{label: 'V2', value: 1}
|
||||
]);
|
||||
});
|
||||
test('Toggling a facet which exists with a value which does as well should remove the value from the facet', function () {
|
||||
var field = {};
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
query.add({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
|
||||
|
||||
query.toggle({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
|
||||
|
||||
equal(query.length, 1, 'Should have the same single facet');
|
||||
var facet = query.at(0);
|
||||
equal(facet.values.length, 1, "Should only have one value left in the facet");
|
||||
deepEqual(facet.get('values'), [
|
||||
{label: 'V1', value: 0}
|
||||
]);
|
||||
});
|
||||
test('Toggling off the last value of a facet should remove the facet', function () {
|
||||
var field = {};
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
|
||||
query.toggle({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
|
||||
equal(query.length, 0, 'Should have removed the facet');
|
||||
});
|
||||
test('Intermediate emptyness should not remove the facet', function () {
|
||||
var field = {};
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
|
||||
|
||||
query.toggle({category: 'A', field: field, values: [
|
||||
{label: 'V1', value: 0},
|
||||
{label: 'V2', value: 1}
|
||||
]});
|
||||
|
||||
equal(query.length, 1, 'Should not have removed the facet');
|
||||
var facet = query.at(0);
|
||||
equal(facet.values.length, 1, "Should have one value");
|
||||
deepEqual(facet.get('values'), [
|
||||
{label: 'V2', value: 1}
|
||||
]);
|
||||
});
|
||||
// TODO: if the last FacetValue of a facet is removed, the facet should be removed from the query
|
||||
|
||||
test('Reseting with multiple facets should still work to load defaults', function () {
|
||||
var query = new instance.web.search.SearchQuery;
|
||||
var field = {};
|
||||
query.reset([
|
||||
{category: 'A', field: field, values: [{label: 'V1', value: 0}]},
|
||||
{category: 'B', field: field, values: [{label: 'V2', value: 1}]}]);
|
||||
|
||||
equal(query.length, 1, 'Should have created a single facet');
|
||||
equal(query.at(0).values.length, 2, 'the facet should have merged two values');
|
||||
deepEqual(query.at(0).get('values'), [
|
||||
{label: 'V1', value: 0},
|
||||
{label: 'V2', value: 1}
|
||||
])
|
||||
});
|
||||
});
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<script src="/web/static/lib/underscore/underscore.js" type="text/javascript"></script>
|
||||
<script src="/web/static/lib/underscore/underscore.string.js" type="text/javascript"></script>
|
||||
<script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script>
|
||||
|
||||
<!-- jquery -->
|
||||
<script src="/web/static/lib/jquery/jquery-1.6.4.js"></script>
|
||||
|
@ -53,4 +54,5 @@
|
|||
<script type="text/javascript" src="/web/static/test/formats.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/rpc.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/evals.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/search.js"></script>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue