diff options
| author | Julien Mougenot <jum@odoo.com> | 2020-07-31 11:52:25 +0000 |
|---|---|---|
| committer | Julien Mougenot <jum@odoo.com> | 2020-08-04 12:46:27 +0000 |
| commit | 2524c7aaace5ce314eace5517285bf13586fb176 (patch) | |
| tree | 5c2db53a8cb2bab5cb8459ba5ef42d3c4b0cef64 /addons/web/static/src | |
| parent | cc6a60bec935a42e19bac0bef5771dfcbad81f83 (diff) | |
[IMP] web: Do not store search panel categories
This commit handles two things:
1) Move the local storage logic of the search panel into the documents
module.
2) This allows for a variety of optimisations now that we do not have to
constantly wait for a potential default value hidden in the storage.
This is why this commit also introduces a way for the search panel model
extension to sometimes return its query informations sooner than after
being completely loaded.
Task 2284207
Diffstat (limited to 'addons/web/static/src')
3 files changed, 170 insertions, 153 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index 749feacb3d6..ee39d7cb7a7 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -693,7 +693,6 @@ ActionManager.include({ if (config.device.isMobile && actionData.mobile) { options = Object.assign({}, options, actionData.mobile); } - action.flags = _.extend({}, action.flags, {searchPanelDefaultNoFilter: true}); return self.doAction(action, options).then(ev.data.on_success, ev.data.on_fail); }); }, diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 129cb6817b3..127b2a10c45 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -260,17 +260,8 @@ var AbstractView = Factory.extend({ if (this.withSearchPanel) { // Search panel (Model) const SearchPanelComponent = this.config.SearchPanel; - const defaultValues = {}; - Object.keys(this.loadParams.context).forEach((key) => { - let match = /^searchpanel_default_(.*)$/.exec(key); - if (match) { - defaultValues[match[1]] = this.loadParams.context[key]; - } - }); extensions[SearchPanelComponent.modelExtension] = { archNodes: searchPanelInfo.children, - defaultNoFilter: params.searchPanelDefaultNoFilter, - defaultValues, }; this.controllerParams.withSearchPanel = true; this.rendererParams.withSearchPanel = true; diff --git a/addons/web/static/src/js/views/search_panel_model_extension.js b/addons/web/static/src/js/views/search_panel_model_extension.js index 3641c425ba7..3aca92029f3 100644 --- a/addons/web/static/src/js/views/search_panel_model_extension.js +++ b/addons/web/static/src/js/views/search_panel_model_extension.js @@ -14,6 +14,14 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function let nextSectionId = 1; /** + * @param {Filter} filter + * @returns {boolean} + */ + function hasDomain(filter) { + return filter.domain !== "[]"; + } + + /** * @param {Section} section * @returns {boolean} */ @@ -68,8 +76,17 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function constructor() { super(...arguments); - this.searchDomain = null; - this.DEFAULT_VALUE_INDEX = 0; + this.categoriesToLoad = []; + this.defaultValues = {}; + this.filtersToLoad = []; + this.initialStateImport = false; + this.searchDomain = []; + for (const key in this.config.context) { + const match = /^searchpanel_default_(.*)$/.exec(key); + if (match) { + this.defaultValues[match[1]] = this.config.context[key]; + } + } } //--------------------------------------------------------------------- @@ -79,6 +96,27 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function /** * @override */ + async callLoad(params) { + const searchDomain = this._getExternalDomain(); + params.searchDomainChanged = ( + JSON.stringify(this.searchDomain) !== JSON.stringify(searchDomain) + ); + if (!this.shouldLoad && !this.initialStateImport) { + const isFetchable = (section) => section.enableCounters || + (params.searchDomainChanged && !section.expand); + this.categoriesToLoad = this.categories.filter(isFetchable); + this.filtersToLoad = this.filters.filter(isFetchable); + this.shouldLoad = params.searchDomainChanged || + Boolean(this.categoriesToLoad.length + this.filtersToLoad.length); + } + this.searchDomain = searchDomain; + this.initialStateImport = false; + await super.callLoad(params); + } + + /** + * @override + */ exportState() { const state = super.exportState(); state.sections = [...state.sections]; @@ -109,6 +147,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function * @override */ importState(importedState) { + this.initialStateImport = Boolean(importedState && !this.state.sections); super.importState(...arguments); if (importedState) { this.state.sections = new Map(this.state.sections); @@ -127,23 +166,26 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function /** * @override */ - async load() { - await this._fetchSections({ initialLoad: true }); + async isReady() { + await this.sectionsPromise; } /** * @override */ - prepareState() { - Object.assign(this.state, { sections: new Map() }); - this._createSectionsFromArch(); + async load(params) { + this.sectionsPromise = this._fetchSections(params.isInitialLoad); + if (this._shouldWaitForData(params)) { + await this.sectionsPromise; + } } /** * @override */ - async reloadAfterDispatch() { - await this._fetchSections(); + prepareState() { + Object.assign(this.state, { sections: new Map() }); + this._createSectionsFromArch(); } //--------------------------------------------------------------------- @@ -180,18 +222,13 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function } /** - * Sets the active value id of a given category. This will also update - * the local storage value. + * Sets the active value id of a given category. * @param {number} sectionId * @param {number} valueId */ toggleCategoryValue(sectionId, valueId) { const category = this.state.sections.get(sectionId); category.activeValueId = valueId; - const storageKey = `searchpanel_${this.config.modelName}_${category.fieldName}`; - this.env.services.local_storage.setItem(storageKey, valueId); - // FIXME this should be removed (in another commit): should no longer (re)store - // active category in localStorage } /** @@ -240,7 +277,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function */ _applyDefaultFilterValues() { for (const { fieldName, values } of this.filters) { - const defaultValues = this.config.defaultValues[fieldName] || []; + const defaultValues = this.defaultValues[fieldName] || []; for (const valueId of defaultValues) { const value = values.get(valueId); if (value) { @@ -266,15 +303,6 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function if (category.hierarchize) { category.parentField = parentField; } - - const allItem = { - childrenIds: [], - display_name: this.env._t("All"), - id: false, - bold: true, - parentId: false, - }; - category.values = new Map([[false, allItem]]); for (const value of values) { category.values.set( value.id, @@ -299,7 +327,8 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function } } // Set active value from context - category.activeValueId = this._getCategoryDefaultValue(values, category); + const valueIds = [false, ...values.map((val) => val.id)]; + this._ensureCategoryValue(category, valueIds); } /** @@ -318,8 +347,8 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function // restore checked property values.forEach((value) => { - const oldValue = filter.values && filter.values.get(value.id); - value.checked = (oldValue && oldValue.checked) || false; + const oldValue = filter.values.get(value.id); + value.checked = oldValue ? oldValue.checked : false; }); filter.values = new Map(); @@ -390,12 +419,21 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function index, limit: pyUtils.py_eval(attrs.limit || String(DEFAULT_LIMIT)), type, + values: new Map(), }; if (type === "category") { + section.activeValueId = this.defaultValues[attrs.name]; section.icon = section.icon || "fa-folder"; section.hierarchize = !!pyUtils.py_eval( attrs.hierarchize || "1" ); + section.values.set(false, { + childrenIds: [], + display_name: this.env._t("All"), + id: false, + bold: true, + parentId: false, + }); } else { section.domain = attrs.domain || "[]"; section.groupBy = attrs.groupby; @@ -406,103 +444,96 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function } /** + * Ensures that the active value of a category is one of its own + * existing values. + * @private + * @param {Category} category + * @param {number[]} valueIds + */ + _ensureCategoryValue(category, valueIds) { + if (!valueIds.includes(category.activeValueId)) { + category.activeValueId = valueIds[0]; + } + } + + /** * Fetches values for each category at startup. At reload a category is * only fetched if needed. * @private - * @param {{ - * initialLoad: boolean, - * searchDomainHasChanged: boolean - * }} options + * @param {Category[]} categories * @returns {Promise} resolved when all categories have been fetched */ - async _fetchCategories({ initialLoad, searchDomainHasChanged }) { - const proms = []; - for (const category of this.categories) { - const { enableCounters, expand, hierarchize, limit } = category; - if (initialLoad || enableCounters || (searchDomainHasChanged && !expand)) { - const prom = this.env.services.rpc({ - method: "search_panel_select_range", - model: this.config.modelName, - args: [category.fieldName], - kwargs: { - category_domain: this._getCategoryDomain( - category.id - ), - enable_counters: enableCounters, - expand, - filter_domain: this._getFilterDomain(), - hierarchize, - limit, - search_domain: this.searchDomain, - }, - }).then(result => this._createCategoryTree(category.id, result)); - proms.push(prom); - } - } - await Promise.all(proms); + async _fetchCategories(categories) { + const filterDomain = this._getFilterDomain(); + await Promise.all(categories.map(async (category) => { + const result = await this.env.services.rpc({ + method: "search_panel_select_range", + model: this.config.modelName, + args: [category.fieldName], + kwargs: { + category_domain: this._getCategoryDomain(category.id), + enable_counters: category.enableCounters, + expand: category.expand, + filter_domain: filterDomain, + hierarchize: category.hierarchize, + limit: category.limit, + search_domain: this.searchDomain, + }, + }); + this._createCategoryTree(category.id, result); + })); } /** * Fetches values for each filter. This is done at startup and at each * reload if needed. * @private - * @param {{ - * initialLoad: boolean, - * searchDomainHasChanged: boolean - * }} options + * @param {Filter[]} filters * @returns {Promise} resolved when all filters have been fetched */ - async _fetchFilters({ initialLoad, searchDomainHasChanged }) { + async _fetchFilters(filters) { const evalContext = {}; for (const category of this.categories) { evalContext[category.fieldName] = category.activeValueId; } const categoryDomain = this._getCategoryDomain(); - const proms = []; - for (const filter of this.filters) { - const { enableCounters, expand, groupBy, limit } = filter; - if (initialLoad || enableCounters || (searchDomainHasChanged && !expand)) { - const prom = this.env.services.rpc({ - method: "search_panel_select_multi_range", - model: this.config.modelName, - args: [filter.fieldName], - kwargs: { - category_domain: categoryDomain, - comodel_domain: Domain.prototype.stringToArray( - filter.domain, - evalContext - ), - enable_counters: enableCounters, - filter_domain: this._getFilterDomain(filter.id), - expand, - group_by: groupBy || false, - group_domain: this._getGroupDomain(filter), - limit, - search_domain: this.searchDomain, - }, - }).then(result => this._createFilterTree(filter.id, result)); - proms.push(prom); - } - } - await Promise.all(proms); + await Promise.all(filters.map(async (filter) => { + const result = await this.env.services.rpc({ + method: "search_panel_select_multi_range", + model: this.config.modelName, + args: [filter.fieldName], + kwargs: { + category_domain: categoryDomain, + comodel_domain: Domain.prototype.stringToArray( + filter.domain, + evalContext + ), + enable_counters: filter.enableCounters, + filter_domain: this._getFilterDomain(filter.id), + expand: filter.expand, + group_by: filter.groupBy || false, + group_domain: this._getGroupDomain(filter), + limit: filter.limit, + search_domain: this.searchDomain, + }, + }); + this._createFilterTree(filter.id, result); + })); } /** - * Fetches categories and filters at startup or if needed. * @private - * @param {Object} [options={}] - * @param {boolean} [options.initialLoad=false] + * @param {boolean} isInitialLoad + * @returns {Promise} */ - async _fetchSections(options = {}) { - const searchDomain = this._getExternalDomain(); - if (!options.initialLoad) { - options.searchDomainHasChanged = - JSON.stringify(this.searchDomain) !== JSON.stringify(searchDomain); - } - this.searchDomain = searchDomain; - await this._fetchCategories(options); - await this._fetchFilters(options); - if (options.initialLoad) { + async _fetchSections(isInitialLoad) { + await this._fetchCategories( + isInitialLoad ? this.categories : this.categoriesToLoad + ); + await this._fetchFilters( + isInitialLoad ? this.filters : this.filtersToLoad + ); + if (isInitialLoad) { this._applyDefaultFilterValues(); } } @@ -520,54 +551,23 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function for (const category of this.categories) { if ( category.id === excludedCategoryId || - category.activeValueId === false + !category.activeValueId ) { continue; } - if (category.activeValueId) { - const field = this.config.fields[category.fieldName]; - const operator = - field.type === "many2one" && category.parentField ? "child_of" : "="; - domain.push([ - category.fieldName, - operator, - category.activeValueId, - ]); - } + const field = this.config.fields[category.fieldName]; + const operator = + field.type === "many2one" && category.parentField ? "child_of" : "="; + domain.push([ + category.fieldName, + operator, + category.activeValueId, + ]); } return domain; } /** - * @private - * @param {Object[]} values - * @param {Category} category - * @returns {number | false} - */ - _getCategoryDefaultValue(values, { fieldName }) { - const validValues = [false, ...values.map((val) => val.id)]; - const defaultValue = this.config.defaultValues[fieldName]; - if (validValues.includes(defaultValue)) { - return defaultValue; - } - if (this.config.defaultNoFilter) { - return false; - } - // If not set in context, or set to an unknown value, set active value - // from localStorage - const storageKey = `searchpanel_${this.config.modelName}_${fieldName}`; - const storageValue = this.env.services.local_storage.getItem(storageKey); - if (validValues.includes(storageValue)) { - return storageValue; - } - // If still not a valid value, get the search panel default value - // from the given valid values. - return validValues[ - Math.min(validValues.length - 1, this.DEFAULT_VALUE_INDEX) - ]; - } - - /** * Returns the domain retrieved from the other model extensions. * @private * @returns {Array[]} @@ -617,7 +617,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function for (const group of groups.values()) { addCondition(fieldName, group.values); } - } else if (values) { + } else { addCondition(fieldName, values); } } @@ -694,6 +694,33 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function return groupDomain; } + /** + * Returns whether the query informations should be considered as ready + * before or after having (re-)fetched the sections data. + * @private + * @param {Object} params + * @param {boolean} params.isInitialLoad + * @param {boolean} params.searchDomainChanged + * @returns {boolean} + */ + _shouldWaitForData({ isInitialLoad, searchDomainChanged }) { + if (isInitialLoad && Object.keys(this.defaultValues).length) { + // Default values need to be checked on initial load + return true; + } + if (this.categories.length && this.filters.some(hasDomain)) { + // Selected category value might affect the filter values + return true; + } + if (!this.searchDomain.length) { + // No search domain -> no need to check for expand + return false; + } + return [...this.state.sections.values()].some( + (section) => !section.expand && searchDomainChanged + ); + } + //--------------------------------------------------------------------- // Static //--------------------------------------------------------------------- |
