diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 830253e0622..d6ec3bc981f 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -249,69 +249,10 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea /** * Sets up thingie where all the mess is put? */ - setup_stuff_drawer: function () { - var self = this; - $('
').appendTo(this.$element); - var $drawer = $('
').appendTo(this.$element); - var $filters = $('
').appendTo($drawer); - - var running_count = 0; - // get total filters count - var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; - var filters_count = _(this.controls).chain() - .flatten() - .filter(is_group) - .map(function (i) { return i.filters.length; }) - .sum() - .value(); - - var col1 = [], col2 = _(this.controls).map(function (inputs, group) { - var filters = _(inputs).filter(is_group); - return { - name: group === 'null' ? _t("Filters") : group, - filters: filters, - length: _(filters).chain().map(function (i) { - return i.filters.length; }).sum().value() - }; + select_for_drawer: function () { + return _(this.inputs).filter(function (input) { + return input.in_drawer(); }); - - 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; - } - } - - // Create a Custom Filter FilterGroup for each custom filter read from - // the db, add all of this as a group in the smallest column - [].push.call(col1.length <= col2.length ? col1 : col2, { - name: _t("Custom Filters"), - filters: _.map(this.custom_filters, function (filter) { - // FIXME: handling of ``disabled`` being set - var f = new instance.web.search.Filter({attrs: { - string: filter.name, - context: filter.context, - domain: filter.domain - }}, self); - return new instance.web.search.FilterGroup([f], self); - }), - length: 3 - }); - - return $.when( - this.render_column(col1, $('
').appendTo($filters)), - this.render_column(col2, $('
').appendTo($filters)), - (new instance.web.search.Advanced(this).appendTo($drawer))); - }, - render_column: function (column, $el) { - return $.when.apply(null, _(column).map(function (group) { - $('

').text(group.name).appendTo($el); - return $.when.apply(null, - _(group.filters).invoke('appendTo', $el)); - })); }, /** * Sets up search view's view-wide auto-completion widget @@ -479,14 +420,24 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea data.fields_view['arch'].children, data.fields_view.fields); + // add Filters to this.inputs, need view.controls filled + var filters = new instance.web.search.Filters(this); + // add Advanced to this.inputs + var advanced = new instance.web.search.Advanced(this); + + // build drawer + var drawer_started = $.when.apply( + null, _(this.select_for_drawer()).invoke( + 'appendTo', this.$element.find('.oe_searchview_drawer'))); + // load defaults - return $.when( - this.setup_stuff_drawer(), - $.when.apply(null, _(this.inputs).invoke('facet_for_defaults', this.defaults)) - .then(function () { - self.query.reset(_(arguments).compact(), {silent: true}); - self.renderFacets(); - })) + var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( + 'facet_for_defaults', this.defaults)).then(function () { + self.query.reset(_(arguments).compact(), {silent: true}); + self.renderFacets(); + }); + + return $.when(drawer_started, defaults_fetched) .then(function () { self.ready.resolve(); }) }, /** @@ -779,6 +730,7 @@ instance.web.search.Group = instance.web.search.Widget.extend({ }); instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ + _in_drawer: false, /** * @constructs instance.web.search.Input * @extends instance.web.search.Widget @@ -820,6 +772,9 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan } return this.facet_for(defaults[this.attrs.name]); }, + in_drawer: function () { + return !!this._in_drawer; + }, get_context: function () { throw new Error( "get_context not implemented for widget " + this.attrs.type); @@ -937,6 +892,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in var self = this, fs; var facet = this.view.query.detect(function (f) { return f.get('field') === self; }); + // just toggle the bloody thing if (facet) { fs = facet.get('values'); @@ -1304,8 +1260,71 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ } }); +instance.web.search.Filters = instance.web.search.Input.extend({ + template: 'SearchView.Filters', + _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 filters_count = _(this.view.controls).chain() + .flatten() + .filter(is_group) + .map(function (i) { return i.filters.length; }) + .sum() + .value(); + + var col1 = [], col2 = _(this.view.controls).map(function (inputs, group) { + var filters = _(inputs).filter(is_group); + return { + name: group === 'null' ? _t("Filters") : group, + filters: filters, + length: _(filters).chain().map(function (i) { + return i.filters.length; }).sum().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; + } + } + + // Create a Custom Filter FilterGroup for each custom filter read from + // the db, add all of this as a group in the smallest column + (col1.length <= col2.length ? col1 : col2).push({ + name: _t("Custom Filters"), + filters: _.map(this.view.custom_filters, function (filter) { + // FIXME: handling of ``disabled`` being set + var f = new instance.web.search.Filter({attrs: { + string: filter.name, + context: filter.context, + domain: filter.domain + }}, self.view); + return new instance.web.search.FilterGroup([f], self.view); + }), + length: this.view.custom_filters.length + }); + return $.when( + this.render_column(col1, $('
').appendTo(this.$element)), + this.render_column(col2, $('
').appendTo(this.$element))); + }, + render_column: function (column, $el) { + return $.when.apply(null, _(column).map(function (group) { + $('

').text(group.name).appendTo($el); + return $.when.apply(null, + _(group.filters).invoke('appendTo', $el)); + })); + } +}); instance.web.search.Advanced = instance.web.search.Input.extend({ template: 'SearchView.advanced', + _in_drawer: true, start: function () { var self = this; this.$element diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index fcdf68a12ca..c275903c4a3 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1291,6 +1291,8 @@
+
+
+
+ +

Advanced Search...

diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index ec93f1a6707..9e4c0b72747 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -170,12 +170,24 @@ $(document).ready(function () { } }); + /** + * Builds a basic search view with a single "dummy" field. The dummy + * extends `instance.web.search.Field`, it does not add any (class) + * attributes beyond what is provided through ``dummy_widget_attributes``. + * + * The view is returned un-started, it is the caller's role to start it + * (or use DOM-insertion methods to start it indirectly). + * + * @param [dummy_widget_attributes={}] + * @param [defaults={}] + * @return {instance.web.SearchView} + */ function makeSearchView(dummy_widget_attributes, defaults) { instance.web.search.fields.add( 'dummy', 'instance.dummy.DummyWidget'); instance.dummy = {}; instance.dummy.DummyWidget = instance.web.search.Field.extend( - dummy_widget_attributes); + dummy_widget_attributes || {}); instance.connection.responses['/web/searchview/load'] = function () { return {result: {fields_view: { type: 'search', @@ -642,6 +654,42 @@ $(document).ready(function () { }); }); - // TODO: test drawer rendering + module('drawer', { + 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); + + instance.web.qweb.add_template(doc); + + instance.connection.responses = {}; + instance.connection.rpc_function = function (url, payload) { + if (!(url.url in this.responses)) { + return $.Deferred().reject( + {}, 'failed', + _.str.sprintf("Url %s not found in mock responses", + url.url)).promise(); + } + return $.when(this.responses[url.url](payload)); + }; + } + }); + asyncTest('is-drawn', 2, function () { + var view = makeSearchView(); + var $fix = $('#qunit-fixture'); + view.appendTo($fix) + .always(start) + .fail(function (error) { ok(false, error.message); }) + .done(function () { + ok($fix.find('.oe_searchview_filters').length, + "filters drawer control has been drawn"); + ok($fix.find('.oe_searchview_advanced').length, + "filters advanced search has been drawn"); + }); + }); + // TODO: UI tests? }); diff --git a/doc/search-view.rst b/doc/search-view.rst index 9d5944d41d6..a7fbb77f51b 100644 --- a/doc/search-view.rst +++ b/doc/search-view.rst @@ -122,12 +122,18 @@ rendered inside the drawer. second more usual calendar widget in the drawer for more obvious/precise interactions) -Any input can note its desire to be rendered in the drawer by setting -its :js:attr:`~openerp.web.search.Input.in_drawer` attribute to -``true``, either on its class or on its instance. +Any input can note its desire to be rendered in the drawer by +returning a truthy value from +:js:func:`~openerp.web.search.Input.in_drawer`. -It will be rendered in the full width of the drawer, and instantiated -only once. +By default, :js:func:`~openerp.web.search.Input.in_drawer` returns the +value of :js:attr:`~openerp.web.search.Input._in_drawer`, which is +``false``. The behavior can be toggled either by redefining the +attribute to ``true`` (either on the class or on the input), or by +overriding :js:func:`~openerp.web.search.Input.in_drawer` itself. + +The input will be rendered in the full width of the drawer, it will be +started only once (per view). .. todo:: drawer API (if a widget wants to close the drawer in some way), part of the low-level SearchView API/interactions?