X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fweb%2Fstatic%2Fsrc%2Fjs%2Fsearch.js;h=07982cb07f7d5ae2cc42b80f86c489bb2eb56106;hb=d9e48bae42f84d0880ac1be66c31c50be08d9417;hp=9f11fdf741233e3d69c855efe0a4d5167f6c960a;hpb=7ab413724acd917be5bfacf734ed4dbd9098102e;p=odoo%2Fodoo.git diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 9f11fdf..07982cb 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -29,8 +29,8 @@ my.Facet = B.Model.extend({ B.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.values.on('add remove change reset', function (_, options) { + this.trigger('change', this, options); }, this); }, get: function (key) { @@ -340,7 +340,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }, 'click .oe_searchview_unfold_drawer': function (e) { e.stopImmediatePropagation(); - this.$el.toggleClass('oe_searchview_open_drawer'); + if (this.drawer) + this.drawer.toggle(); }, 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { switch(e.which) { @@ -349,7 +350,9 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea e.preventDefault(); break; case $.ui.keyCode.RIGHT: - this.focusFollowing(e.target); + if (!this.autocomplete.is_expandable()) { + this.focusFollowing(e.target); + } e.preventDefault(); break; } @@ -383,23 +386,29 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea this.defaults = defaults || {}; this.has_defaults = !_.isEmpty(this.defaults); - this.inputs = []; - this.controls = []; - this.headless = this.options.hidden && !this.has_defaults; this.input_subviews = []; + this.view_manager = null; + this.$view_manager_header = null; this.ready = $.Deferred(); + this.drawer_ready = $.Deferred(); + this.fields_view_get = $.Deferred(); + this.drawer = new instance.web.SearchViewDrawer(parent, this); + }, start: function() { var self = this; var p = this._super(); + this.$view_manager_header = this.$el.parents(".oe_view_manager_header").first(); + this.setup_global_completion(); this.query = new my.SearchQuery() .on('add change reset remove', this.proxy('do_search')) - .on('add change reset remove', this.proxy('renderFacets')); + .on('change', this.proxy('renderChangedFacets')) + .on('add reset remove', this.proxy('renderFacets')); if (this.options.hidden) { this.$el.hide(); @@ -415,20 +424,32 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); this.alive($.when(load_view)).then(function (r) { + self.fields_view_get.resolve(r); return self.search_view_loaded(r); }).fail(function () { self.ready.reject.apply(null, arguments); }); } - instance.web.bus.on('click', this, function(ev) { - if ($(ev.target).parents('.oe_searchview').length === 0) { - self.$el.removeClass('oe_searchview_open_drawer'); - } - }); + var view_manager = this.getParent(); + while (!(view_manager instanceof instance.web.ViewManager) && + view_manager && view_manager.getParent) { + view_manager = view_manager.getParent(); + } + if (view_manager) { + this.view_manager = view_manager; + view_manager.on('switch_mode', this, function (e) { + self.drawer.toggle(e === 'graph'); + }); + } return $.when(p, this.ready); }, + + set_drawer: function (drawer) { + this.drawer = drawer; + }, + show: function () { this.$el.show(); }, @@ -462,65 +483,20 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }, /** - * Sets up thingie where all the mess is put? - */ - select_for_drawer: function () { - return _(this.inputs).filter(function (input) { - return input.in_drawer(); - }); - }, - /** * Sets up search view's view-wide auto-completion widget */ setup_global_completion: function () { var self = this; - var autocomplete = this.$el.autocomplete({ + this.autocomplete = new instance.web.search.AutoComplete(this, { source: this.proxy('complete_global_search'), select: this.proxy('select_completion'), - focus: function (e) { e.preventDefault(); }, - html: true, - autoFocus: true, - minLength: 1, - delay: 250, - }).data('autocomplete'); - - this.$el.on('input', function () { - this.$el.autocomplete('close'); - }.bind(this)); - - // MonkeyPatch autocomplete instance - _.extend(autocomplete, { - _renderItem: function (ul, item) { - // item of completion list - var $item = $( "
" ) - .data( "item.autocomplete", item ) - .appendTo( ul ); - - if (item.facet !== undefined) { - // regular completion item - if (item.first) { - $item.css('borderTop', '1px solid #cccccc'); - } - return $item.append( - (item.label) - ? $('').html(item.label) - : $('').text(item.value)); - } - return $item.text(item.label) - .css({ - borderTop: '1px solid #cccccc', - margin: 0, - padding: 0, - zoom: 1, - 'float': 'left', - clear: 'left', - width: '100%' - }); - }, - _value: function() { + delay: 0, + get_search_string: function () { return self.$('div.oe_searchview_input').text(); }, + width: this.$el.width(), }); + this.autocomplete.appendTo(this.$el); }, /** * Provide auto-completion result for req.term (an array to `resp`) @@ -530,18 +506,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * @param {Function} resp response callback */ complete_global_search: function (req, resp) { - $.when.apply(null, _(this.inputs).chain() + $.when.apply(null, _(this.drawer.inputs).chain() .filter(function (input) { return input.visible(); }) .invoke('complete', req.term) .value()).then(function () { resp(_(arguments).chain() .compact() - .map(function (completion) { - if (completion.length && completion[0].facet !== undefined) { - completion[0].first = true; - } - return completion; - }) .flatten(true) .value()); }); @@ -569,24 +539,25 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea childBlurred: function () { var val = this.$el.val(); this.$el.val(''); - var complete = this.$el.data('autocomplete'); - if ((val && complete.term === undefined) || complete.previous) { - throw new Error("new jquery.ui version altering implementation" + - " details relied on"); - } - delete complete.term; this.$el.removeClass('oe_focused') .trigger('blur'); + this.autocomplete.close(); }, /** - * - * @param {openerp.web.search.SearchQuery | openerp.web.search.Facet} _1 - * @param {openerp.web.search.Facet} [_2] + * Call the renderFacets method with the correct arguments. + * This is due to the fact that change events are called with two arguments + * (model, options) while add, reset and remove events are called with + * (collection, model, options) as arguments + */ + renderChangedFacets: function (model, options) { + this.renderFacets(undefined, model, options); + }, + /** + * @param {openerp.web.search.SearchQuery | undefined} Undefined if event is change + * @param {openerp.web.search.Facet} * @param {Object} [options] */ - renderFacets: function (_1, _2, options) { - // _1: model if event=change, otherwise collection - // _2: undefined if event=change, otherwise model + renderFacets: function (collection, model, options) { var self = this; var started = []; var $e = this.$('div.oe_searchview_facets'); @@ -611,6 +582,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); $.when.apply(null, started).then(function () { + if (options && options.focus_input === false) return; var input_to_focus; // options.at: facet inserted at given index, focus next input // otherwise just focus last input @@ -619,92 +591,10 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } else { input_to_focus = self.input_subviews[(options.at + 1) * 2]; } - input_to_focus.$el.focus(); }); }, - /** - * Builds a list of widget rows (each row is an array of widgets) - * - * @param {Array} items a list of nodes to convert to widgets - * @param {Object} fields a mapping of field names to (ORM) field attributes - * @param {Object} [group] group to put the new controls in - */ - make_widgets: function (items, fields, group) { - if (!group) { - group = new instance.web.search.Group( - this, 'q', {attrs: {string: _t("Filters")}}); - } - var self = this; - var filters = []; - _.each(items, function (item) { - if (filters.length && item.tag !== 'filter') { - group.push(new instance.web.search.FilterGroup(filters, group)); - filters = []; - } - - switch (item.tag) { - case 'separator': case 'newline': - break; - case 'filter': - filters.push(new instance.web.search.Filter(item, group)); - break; - case 'group': - self.make_widgets(item.children, fields, - new instance.web.search.Group(group, 'w', item)); - break; - case 'field': - var field = this.make_field( - item, fields[item['attrs'].name], group); - group.push(field); - // filters - self.make_widgets(item.children, fields, group); - break; - } - }, this); - - if (filters.length) { - group.push(new instance.web.search.FilterGroup(filters, this)); - } - }, - /** - * Creates a field for the provided field descriptor item (which comes - * from fields_view_get) - * - * @param {Object} item fields_view_get node for the field - * @param {Object} field fields_get result for the field - * @param {Object} [parent] - * @returns instance.web.search.Field - */ - make_field: function (item, field, parent) { - // M2O combined with selection widget is pointless and broken in search views, - // but has been used in the past for unsupported hacks -> ignore it - if (field.type === "many2one" && item.attrs.widget === "selection"){ - item.attrs.widget = undefined; - } - var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); - if(obj) { - return new (obj) (item, field, parent || this); - } else { - console.group('Unknown field type ' + field.type); - console.error('View node', item); - console.info('View field', field); - console.info('In view', this); - console.groupEnd(); - return null; - } - }, - - add_common_inputs: function() { - // add Filters to this.inputs, need view.controls filled - (new instance.web.search.Filters(this)); - // add custom filters to this.inputs - this.custom_filters = new instance.web.search.CustomFilters(this); - // add Advanced to this.inputs - (new instance.web.search.Advanced(this)); - }, - search_view_loaded: function(data) { var self = this; this.fields_view = data; @@ -714,24 +604,9 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea "Got non-search view after asking for a search view: type %s, arch root %s", data.type, data.arch.tag)); } - this.make_widgets( - data['arch'].children, - data.fields); - - this.add_common_inputs(); - - // build drawer - var drawer_started = $.when.apply( - null, _(this.select_for_drawer()).invoke( - 'appendTo', this.$('.oe_searchview_drawer'))); - - - // load defaults - var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( - 'facet_for_defaults', this.defaults)) - .then(this.proxy('setup_default_query')); - return $.when(drawer_started, defaults_fetched) + return this.drawer_ready + .then(this.proxy('setup_default_query')) .then(function () { self.trigger("search_view_loaded", data); self.ready.resolve(); @@ -741,14 +616,14 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // Hacky implementation of CustomFilters#facet_for_defaults ensure // CustomFilters will be ready (and CustomFilters#filters will be // correctly filled) by the time this method executes. - var custom_filters = this.custom_filters.filters; + var custom_filters = this.drawer.custom_filters.filters; if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) { // Check for any is_default custom filter var personal_filter = _(custom_filters).find(function (filter) { return filter.user_id && filter.is_default; }); if (personal_filter) { - this.custom_filters.toggle_filter(personal_filter, true); + this.drawer.custom_filters.toggle_filter(personal_filter, true); return; } @@ -756,7 +631,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return !filter.user_id && filter.is_default; }); if (global_filter) { - this.custom_filters.toggle_filter(global_filter, true); + this.drawer.custom_filters.toggle_filter(global_filter, true); return; } } @@ -866,9 +741,175 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea on_invalid: function (errors) { this.do_notify(_t("Invalid Search"), _t("triggered from search view")); this.trigger('invalid_search', errors); + }, + + // The method appendTo is overwrited to be able to insert the drawer anywhere + appendTo: function ($searchview_parent, $searchview_drawer_node) { + var $searchview_drawer_node = $searchview_drawer_node || $searchview_parent; + + return $.when( + this._super($searchview_parent), + this.drawer.appendTo($searchview_drawer_node) + ); + }, + + destroy: function () { + this.drawer.destroy(); + this.getParent().destroy.call(this); } }); +instance.web.SearchViewDrawer = instance.web.Widget.extend({ + template: "SearchViewDrawer", + + init: function(parent, searchview) { + this._super(parent); + this.searchview = searchview; + this.searchview.set_drawer(this); + this.ready = searchview.drawer_ready; + this.controls = []; + this.inputs = []; + }, + + toggle: function (visibility) { + this.$el.toggle(visibility); + var $view_manager_body = this.$el.closest('.oe_view_manager_body'); + if ($view_manager_body.length) { + $view_manager_body.scrollTop(0); + } + }, + + start: function() { + var self = this; + if (this.searchview.headless) return $.when(this._super(), this.searchview.ready); + var filters_ready = this.searchview.fields_view_get + .then(this.proxy('prepare_filters')); + return $.when(this._super(), filters_ready).then(function () { + var defaults = arguments[1][0]; + self.ready.resolve.apply(null, defaults); + }); + }, + prepare_filters: function (data) { + this.make_widgets( + data['arch'].children, + data.fields); + + this.add_common_inputs(); + + // build drawer + var in_drawer = this.select_for_drawer(); + + var $first_col = this.$(".col-md-7"), + $snd_col = this.$(".col-md-5"); + + var add_custom_filters = in_drawer[0].appendTo($first_col), + add_filters = in_drawer[1].appendTo($first_col), + add_rest = $.when.apply(null, _(in_drawer.slice(2)).invoke('appendTo', $snd_col)), + defaults_fetched = $.when.apply(null, _(this.inputs).invoke( + 'facet_for_defaults', this.searchview.defaults)); + + return $.when(defaults_fetched, add_custom_filters, add_filters, add_rest); + }, + /** + * Sets up thingie where all the mess is put? + */ + select_for_drawer: function () { + return _(this.inputs).filter(function (input) { + return input.in_drawer(); + }); + }, + + /** + * Builds a list of widget rows (each row is an array of widgets) + * + * @param {Array} items a list of nodes to convert to widgets + * @param {Object} fields a mapping of field names to (ORM) field attributes + * @param {Object} [group] group to put the new controls in + */ + make_widgets: function (items, fields, group) { + if (!group) { + group = new instance.web.search.Group( + this, 'q', {attrs: {string: _t("Filters")}}); + } + var self = this; + var filters = []; + _.each(items, function (item) { + if (filters.length && item.tag !== 'filter') { + group.push(new instance.web.search.FilterGroup(filters, group)); + filters = []; + } + + switch (item.tag) { + case 'separator': case 'newline': + break; + case 'filter': + filters.push(new instance.web.search.Filter(item, group)); + break; + case 'group': + self.add_separator(); + self.make_widgets(item.children, fields, + new instance.web.search.Group(group, 'w', item)); + self.add_separator(); + break; + case 'field': + var field = this.make_field( + item, fields[item['attrs'].name], group); + group.push(field); + // filters + self.make_widgets(item.children, fields, group); + break; + } + }, this); + + if (filters.length) { + group.push(new instance.web.search.FilterGroup(filters, this)); + } + }, + + add_separator: function () { + if (!(_.last(this.inputs) instanceof instance.web.search.Separator)) + new instance.web.search.Separator(this); + }, + /** + * Creates a field for the provided field descriptor item (which comes + * from fields_view_get) + * + * @param {Object} item fields_view_get node for the field + * @param {Object} field fields_get result for the field + * @param {Object} [parent] + * @returns instance.web.search.Field + */ + make_field: function (item, field, parent) { + // M2O combined with selection widget is pointless and broken in search views, + // but has been used in the past for unsupported hacks -> ignore it + if (field.type === "many2one" && item.attrs.widget === "selection"){ + item.attrs.widget = undefined; + } + var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); + if(obj) { + return new (obj) (item, field, parent || this); + } else { + console.group('Unknown field type ' + field.type); + console.error('View node', item); + console.info('View field', field); + console.info('In view', this); + console.groupEnd(); + return null; + } + }, + + add_common_inputs: function() { + // add custom filters to this.inputs + this.custom_filters = new instance.web.search.CustomFilters(this); + // add Filters to this.inputs, need view.controls filled + (new instance.web.search.Filters(this)); + (new instance.web.search.SaveFilter(this, this.custom_filters)); + // add Advanced to this.inputs + (new instance.web.search.Advanced(this)); + }, + +}); + /** * Registry of search fields, called by :js:class:`instance.web.SearchView` to * find and instantiate its field widgets. @@ -926,9 +967,10 @@ instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web this._super(parent); var ancestor = parent; do { - this.view = ancestor; - } while (!(ancestor instanceof instance.web.SearchView) + this.drawer = ancestor; + } while (!(ancestor instanceof instance.web.SearchViewDrawer) && (ancestor = (ancestor.getParent && ancestor.getParent()))); + this.view = this.drawer.searchview || this.drawer; } }); @@ -950,7 +992,7 @@ instance.web.search.Group = instance.web.search.Widget.extend({ this.name = attrs.string; this.children = []; - this.view.controls.push(this); + this.drawer.controls.push(this); }, push: function (input) { this.children.push(input); @@ -971,7 +1013,7 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan init: function (parent) { this._super(parent); this.load_attrs({}); - this.view.inputs.push(this); + this.drawer.inputs.push(this); }, /** * Fetch auto-completion values for the widget. @@ -1082,7 +1124,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in */ search_change: function () { var self = this; - var $filters = this.$('> li').removeClass('oe_selected'); + var $filters = this.$('> li').removeClass('badge'); var facet = this.view.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { @@ -1090,7 +1132,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in if (i === -1) { return; } $filters.filter(function () { return Number($(this).data('index')) === i; - }).addClass('oe_selected'); + }).addClass('badge'); }); }, /** @@ -1270,6 +1312,15 @@ instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instanc get_context: function () { }, get_domain: function () { }, }); + +instance.web.search.Separator = instance.web.search.Input.extend({ + _in_drawer: false, + + complete: function () { + return {is_separator: true}; + } +}); + instance.web.search.Field = instance.web.search.Input.extend( /** @lends instance.web.search.Field# */ { template: 'SearchView.field', default_operator: '=', @@ -1564,26 +1615,42 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ this._super(view_section, field, parent); this.model = new instance.web.Model(this.attrs.relation); }, - complete: function (needle) { + + complete: function (value) { + if (_.isEmpty(value)) { return $.when(null); } + var label = _.str.sprintf(_.str.escapeHTML( + _t("Search %(field)s for: %(value)s")), { + field: '' + _.escape(this.attrs.string) + '', + value: '' + _.escape(value) + ''}); + return $.when([{ + label: label, + facet: { + category: this.attrs.string, + field: this, + values: [{label: value, value: value, operator: 'ilike'}] + }, + expand: this.expand.bind(this), + }]); + }, + + expand: function (needle) { var self = this; // FIXME: "concurrent" searches (multiple requests, mis-ordered responses) var context = instance.web.pyeval.eval( 'contexts', [this.view.dataset.get_context()]); return this.model.call('name_search', [], { name: needle, - args: instance.web.pyeval.eval( - 'domains', this.attrs.domain ? [this.attrs.domain] : [], context), + args: (typeof this.attrs.domain === 'string') ? [] : this.attrs.domain, limit: 8, context: context }).then(function (results) { if (_.isEmpty(results)) { return null; } - return [{label: self.attrs.string}].concat( - _(results).map(function (result) { - return { - label: _.escape(result[1]), - facet: facet_from(self, result) - }; - })); + return _(results).map(function (result) { + return { + label: _.escape(result[1]), + facet: facet_from(self, result) + }; + }); }); }, facet_for: function (value) { @@ -1607,9 +1674,13 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ return facetValue.get('label'); }, make_domain: function (name, operator, facetValue) { + operator = facetValue.get('operator') || operator; + switch(operator){ case this.default_operator: return [[name, '=', facetValue.get('value')]]; + case 'ilike': + return [[name, 'ilike', facetValue.get('value')]]; case 'child_of': return [[name, 'child_of', facetValue.get('value')]]; } @@ -1627,11 +1698,11 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ }); instance.web.search.CustomFilters = instance.web.search.Input.extend({ - template: 'SearchView.CustomFilters', + template: 'SearchView.Custom', _in_drawer: true, init: function () { this.is_ready = $.Deferred(); - this._super.apply(this, arguments); + this._super.apply(this,arguments); }, start: function () { var self = this; @@ -1646,18 +1717,15 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ self.clear_selection(); }) .on('reset', this.proxy('clear_selection')); - this.$el.on('submit', 'form', this.proxy('save_current')); - this.$el.on('click', 'input[type=checkbox]', function() { - $(this).siblings('input[type=checkbox]').prop('checked', false); - }); - this.$el.on('click', 'h4', function () { - self.$el.toggleClass('oe_opened'); - }); - return this.model.call('get_filters', [this.view.model]) + return this.model.call('get_filters', [this.view.model, this.get_action_id()]) .then(this.proxy('set_filters')) .done(function () { self.is_ready.resolve(); }) .fail(function () { self.is_ready.reject.apply(self.is_ready, arguments); }); }, + get_action_id: function(){ + var action = instance.client.action_manager.inner_action; + if (action) return action.id; + }, /** * Special implementation delaying defaults until CustomFilters is loaded */ @@ -1677,9 +1745,11 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ * @return {String} mapping key corresponding to the filter */ key_for: function (filter) { - var user_id = filter.user_id; + var user_id = filter.user_id, + action_id = filter.action_id; var uid = (user_id instanceof Array) ? user_id[0] : user_id; - return _.str.sprintf('(%s)%s', uid, filter.name); + var act_id = (action_id instanceof Array) ? action_id[0] : action_id; + return _.str.sprintf('(%s)(%s)%s', uid, act_id, filter.name); }, /** * Generates a :js:class:`~instance.web.search.Facet` descriptor from a @@ -1706,7 +1776,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }; }, clear_selection: function () { - this.$('li.oe_selected').removeClass('oe_selected'); + this.$('span.badge').removeClass('badge'); }, append_filter: function (filter) { var self = this; @@ -1719,12 +1789,13 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } else { var id = filter.id; this.filters[key] = filter; - $filter = this.$filters[key] = $('') + $filter = $('') .appendTo(this.$('.oe_searchview_custom_list')) - .addClass(filter.user_id ? 'oe_searchview_custom_private' - : 'oe_searchview_custom_public') .toggleClass('oe_searchview_custom_default', filter.is_default) - .text(filter.name); + .append(this.$filters[key] = $('').text(filter.name)); + + this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private' + : 'oe_searchview_custom_public') $('x') .click(function (e) { @@ -1736,14 +1807,18 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ $filter.remove(); delete self.$filters[key]; delete self.filters[key]; + if (_.isEmpty(self.filters)) { + self.hide(); + } }); }) .appendTo($filter); } - $filter.unbind('click').click(function () { + this.$filters[key].unbind('click').click(function () { self.toggle_filter(filter); }); + this.show(); }, toggle_filter: function (filter, preventSearch) { var current = this.view.query.find(function (facet) { @@ -1751,15 +1826,44 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }); if (current) { this.view.query.remove(current); - this.$filters[this.key_for(filter)].removeClass('oe_selected'); + this.$filters[this.key_for(filter)].removeClass('badge'); return; } this.view.query.reset([this.facet_for(filter)], { preventSearch: preventSearch || false}); - this.$filters[this.key_for(filter)].addClass('oe_selected'); + this.$filters[this.key_for(filter)].addClass('badge'); }, set_filters: function (filters) { _(filters).map(_.bind(this.append_filter, this)); + if (!filters.length) { + this.hide(); + } + }, + hide: function () { + this.$el.hide(); + }, + show: function () { + this.$el.show(); + }, +}); + +instance.web.search.SaveFilter = instance.web.search.Input.extend({ + template: 'SearchView.SaveFilter', + _in_drawer: true, + init: function (parent, custom_filters) { + this._super(parent); + this.custom_filters = custom_filters; + }, + start: function () { + var self = this; + this.model = new instance.web.Model('ir.filters'); + this.$el.on('submit', 'form', this.proxy('save_current')); + this.$el.on('click', 'input[type=checkbox]', function() { + $(this).siblings('input[type=checkbox]').prop('checked', false); + }); + this.$el.on('click', 'h4', function () { + self.$el.toggleClass('oe_opened'); + }); }, save_current: function () { var self = this; @@ -1792,19 +1896,22 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ model_id: self.view.model, context: results.context, domain: results.domain, - is_default: set_as_default + is_default: set_as_default, + action_id: self.custom_filters.get_action_id() }; // FIXME: current context? return self.model.call('create_or_replace', [filter]).done(function (id) { filter.id = id; - self.append_filter(filter); + if (self.custom_filters) { + self.custom_filters.append_filter(filter); + } self.$el .removeClass('oe_opened') .find('form')[0].reset(); }); }); return false; - } + }, }); instance.web.search.Filters = instance.web.search.Input.extend({ @@ -1812,22 +1919,13 @@ instance.web.search.Filters = instance.web.search.Input.extend({ _in_drawer: true, start: function () { var self = this; - var running_count = 0; - // get total filters count var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; - var visible_filters = _(this.view.controls).chain().reject(function (group) { + var visible_filters = _(this.drawer.controls).chain().reject(function (group) { return _(_(group.children).filter(is_group)).isEmpty() || group.modifiers.invisible; }); - var filters_count = visible_filters - .pluck('children') - .flatten() - .filter(is_group) - .map(function (i) { return i.filters.length; }) - .sum() - .value(); - var col1 = [], col2 = visible_filters.map(function (group) { + var groups = visible_filters.map(function (group) { var filters = _(group.children).filter(is_group); return { name: _.str.sprintf("%s %s", @@ -1838,27 +1936,16 @@ instance.web.search.Filters = instance.web.search.Input.extend({ }; }).value(); - while (col2.length) { - // col1 + group should be smaller than col2 + group - if ((running_count + col2[0].length) <= (filters_count - running_count)) { - running_count += col2[0].length; - col1.push(col2.shift()); - } else { - break; - } - } + var $dl = $('