From 9342b048bf13d71f6e6796943f8f3d2f90021bc0 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 14 Feb 2019 10:37:35 +0000 Subject: [PATCH] [IMP] web: SearchPanel: default filter values in context Before this rev., it was possible to specify default values for categories (fields with attribute select="one") in the context (e.g. searchpanel_default_fieldname: 5). It is now possible to specify default values for filters (select="multi") as well (e.g. searchpanel_default_fieldname: [1,2]). closes odoo/odoo#31107 --- .../static/src/js/views/kanban/kanban_view.js | 6 +- .../src/js/views/kanban/search_panel.js | 81 ++++++++---- .../static/tests/views/search_panel_tests.js | 115 ++++++++++++++++++ 3 files changed, 174 insertions(+), 28 deletions(-) diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index b7484e7d409..37f7fdd42ef 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -143,16 +143,16 @@ var KanbanView = BasicView.extend({ */ _createSearchPanel: function (parent) { var self = this; - var defaultCategoryValues = {}; + var defaultValues = {}; Object.keys(this.loadParams.context).forEach(function (key) { var match = /^searchpanel_default_(.*)$/.exec(key); if (match) { - defaultCategoryValues[match[1]] = self.loadParams.context[key]; + defaultValues[match[1]] = self.loadParams.context[key]; } }); var controlPanelDomain = this.loadParams.domain; var searchPanel = new this.config.SearchPanel(parent, { - defaultCategoryValues: defaultCategoryValues, + defaultValues: defaultValues, fields: this.fields, model: this.loadParams.modelName, searchDomain: controlPanelDomain, diff --git a/addons/web/static/src/js/views/kanban/search_panel.js b/addons/web/static/src/js/views/kanban/search_panel.js index 9062714e0c1..b906d02b89a 100644 --- a/addons/web/static/src/js/views/kanban/search_panel.js +++ b/addons/web/static/src/js/views/kanban/search_panel.js @@ -25,8 +25,8 @@ var SearchPanel = Widget.extend({ /** * @override * @param {Object} params - * @param {Object} [params.defaultCategoryValues={}] the category value to - * activate by default, for each category + * @param {Object} [params.defaultValues={}] the value(s) to activate by + * default, for each filter and category * @param {Object} params.fields * @param {string} params.model * @param {Object} params.sections @@ -42,7 +42,7 @@ var SearchPanel = Widget.extend({ return section.type === 'filter'; }); - this.defaultCategoryValues = params.defaultCategoryValues || {}; + this.defaultValues = params.defaultValues || {}; this.fields = params.fields; this.model = params.model; this.searchDomain = params.searchDomain; @@ -53,7 +53,7 @@ var SearchPanel = Widget.extend({ willStart: function () { var self = this; var loadProm = this._fetchCategories().then(function () { - return self._fetchFilters(); + return self._fetchFilters().then(self._applyDefaultFilterValues.bind(self)); }); return $.when(loadProm, this._super.apply(this, arguments)); }, @@ -100,6 +100,27 @@ var SearchPanel = Widget.extend({ // Private //-------------------------------------------------------------------------- + /** + * Set active values for each filter (coming from context). This needs to be + * done only once, at widget startup. + * + * @private + */ + _applyDefaultFilterValues: function () { + var self = this; + Object.keys(this.filters).forEach(function (filterId) { + var filter = self.filters[filterId]; + var defaultValues = self.defaultValues[filter.fieldName] || []; + defaultValues.forEach(function (value) { + if (filter.values[value]) { + filter.values[value].checked = true; + } + }); + Object.keys(filter.groups || []).forEach(function (groupId) { + self._updateFilterGroupState(filter.groups[groupId]); + }); + }); + }, /** * @private * @param {string} categoryId @@ -131,7 +152,7 @@ var SearchPanel = Widget.extend({ // set active value var validValues = _.pluck(category.values, 'id').concat([false]); // set active value from context - var value = this.defaultCategoryValues[category.fieldName]; + var value = this.defaultValues[category.fieldName]; // if not set in context, or set to an unknown value, set active value // from localStorage if (!_.contains(validValues, value)) { @@ -359,6 +380,22 @@ var SearchPanel = Widget.extend({ } return []; }, + /** + * Compute the current searchPanel domain based on categories and filters, + * and notify environment of the domain change. + * + * Note that this assumes that the environment will update the searchPanel. + * This is done as such to ensure the coordination between the reloading of + * the searchPanel and the reloading of the data. + * + * @private + */ + _notifyDomainUpdated: function () { + this.needReload = true; + this.trigger_up('search_panel_domain_updated', { + domain: this.getDomain(), + }); + }, /** * @private */ @@ -418,20 +455,23 @@ var SearchPanel = Widget.extend({ return $filter; }, /** - * Compute the current searchPanel domain based on categories and filters, - * and notify environment of the domain change. - * - * Note that this assumes that the environment will update the searchPanel. - * This is done as such to ensure the coordination between the reloading of - * the searchPanel and the reloading of the data. + * Updates the state property of a given filter's group according to the + * checked property of its values. * * @private + * @param {Object} group */ - _notifyDomainUpdated: function () { - this.needReload = true; - this.trigger_up('search_panel_domain_updated', { - domain: this.getDomain(), + _updateFilterGroupState: function (group) { + var valuePartition = _.partition(Object.keys(group.values), function (valueId) { + return group.values[valueId].checked; }); + if (valuePartition[0].length && valuePartition[1].length) { + group.state = 'indeterminate'; + } else if (valuePartition[0].length) { + group.state = 'checked'; + } else { + group.state = 'unchecked'; + } }, //-------------------------------------------------------------------------- @@ -481,16 +521,7 @@ var SearchPanel = Widget.extend({ value.checked = !value.checked; var group = filter.groups && filter.groups[value.group_id]; if (group) { - var valuePartition = _.partition(Object.keys(group.values), function (valueId) { - return group.values[valueId].checked; - }); - if (valuePartition[0].length && valuePartition[1].length) { - group.state = 'indeterminate'; - } else if (valuePartition[0].length) { - group.state = 'checked'; - } else { - group.state = 'unchecked'; - } + this._updateFilterGroupState(group); } this._notifyDomainUpdated(); }, diff --git a/addons/web/static/tests/views/search_panel_tests.js b/addons/web/static/tests/views/search_panel_tests.js index 4f9e20f70ac..9814a887b39 100644 --- a/addons/web/static/tests/views/search_panel_tests.js +++ b/addons/web/static/tests/views/search_panel_tests.js @@ -1443,6 +1443,121 @@ QUnit.module('Views', { kanban.destroy(); }); + + QUnit.test('specify active filter values in context', function (assert) { + assert.expect(4); + + var expectedDomain = [ + ['company_id', 'in', [5]], + ['state', 'in', ['abc', 'ghi']], + ]; + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + services: this.services, + arch: '' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (route === '/web/dataset/search_read') { + assert.deepEqual(args.domain, expectedDomain); + } + return this._super.apply(this, arguments); + }, + context: { + searchpanel_default_company_id: [5], + searchpanel_default_state: ['abc', 'ghi'], + }, + }); + + assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 3); + + // manually untick a default value + expectedDomain = [['state', 'in', ['abc', 'ghi']]]; + testUtils.dom.click(kanban.$('.o_search_panel_filter:first .o_search_panel_filter_value:nth(1) input')); + + assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 2); + + kanban.destroy(); + }); + + QUnit.test('retrieved filter value from context does not exist', function (assert) { + assert.expect(1); + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + services: this.services, + arch: '' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (route === '/web/dataset/search_read') { + assert.deepEqual(args.domain, [["company_id", "in", [3]]]); + } + return this._super.apply(this, arguments); + }, + context: { + searchpanel_default_company_id: [1, 3], + }, + }); + + kanban.destroy(); + }); + + QUnit.test('filter with groupby and default values in context', function (assert) { + assert.expect(2); + + this.data.company.records.push({id: 11, name: 'camptocamp', category_id: 7}); + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + services: this.services, + arch: '' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + if (route === '/web/dataset/search_read') { + assert.deepEqual(args.domain, [['company_id', 'in', [5]]]); + } + return this._super.apply(this, arguments); + }, + context: { + searchpanel_default_company_id: [5], + }, + }); + + var secondGroupCheckbox = kanban.$('.o_search_panel_filter_group:nth(1) > div > input').get(0); + assert.strictEqual(secondGroupCheckbox.indeterminate, true); + + kanban.destroy(); + }); }); });