[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
This commit is contained in:
Aaron Bohy
2019-02-14 13:11:56 +00:00
parent 2d83482b9f
commit 9342b048bf
3 changed files with 174 additions and 28 deletions
@@ -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,
@@ -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();
},
@@ -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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates>' +
'<searchpanel>' +
'<field select="multi" name="company_id"/>' +
'<field select="multi" name="state"/>' +
'</searchpanel>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates>' +
'<searchpanel>' +
'<field select="multi" name="company_id"/>' +
'</searchpanel>' +
'</kanban>',
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: '<kanban>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="foo"/>' +
'</div>' +
'</t></templates>' +
'<searchpanel>' +
'<field select="multi" name="company_id" groupby="category_id"/>' +
'</searchpanel>' +
'</kanban>',
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();
});
});
});