diff --git a/web_m2x_options/README.rst b/web_m2x_options/README.rst index fc84ff3d409c..682a36a59b62 100644 --- a/web_m2x_options/README.rst +++ b/web_m2x_options/README.rst @@ -92,6 +92,13 @@ in the field's options dict Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set) +``search_mru`` *boolean* (Default: ``False``) + + Allows to display to the user the 5 lasts records he selected for a given field. This list will be displayed + when the many2one field is focused and when the user didn't type anything to filter on. The values are stored in + the local storage of the browser and updated after a successful saved of the form. This features only works on form + views at the moment. + ir.config_parameter options ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -122,6 +129,15 @@ If you disable one option, you can enable it for particular field by setting "cr Number of displayed lines on all One2many fields +``web_m2x_options.search_mru`` *boolean* (Default: default value is ``False``) + + Enable MRU for all many2one fields (form view only). + +``web_m2x_options.search_mru_max_length`` *int* + + Changes the length of the records to store and show with MRU feature + + To add these parameters go to Configuration -> Technical -> Parameters -> System Parameters and add new parameters like: - web_m2x_options.create: False @@ -130,6 +146,7 @@ To add these parameters go to Configuration -> Technical -> Parameters -> System - web_m2x_options.limit: 10 - web_m2x_options.search_more: True - web_m2x_options.field_limit_entries: 5 +- web_m2x_options.search_mru: True Example diff --git a/web_m2x_options/__manifest__.py b/web_m2x_options/__manifest__.py index 28161df9bf3a..9233ebdad97a 100644 --- a/web_m2x_options/__manifest__.py +++ b/web_m2x_options/__manifest__.py @@ -16,6 +16,11 @@ "website": "https://github.com/OCA/web", "license": "AGPL-3", "depends": ["web"], - "assets": {"web.assets_backend": ["web_m2x_options/static/src/components/*"]}, + "assets": { + "web.assets_backend": [ + "web_m2x_options/static/src/components/*", + "web_m2x_options/static/src/utils/mru.esm.js", + ] + }, "installable": True, } diff --git a/web_m2x_options/models/ir_config_parameter.py b/web_m2x_options/models/ir_config_parameter.py index c24506dd866b..2f694facb79f 100644 --- a/web_m2x_options/models/ir_config_parameter.py +++ b/web_m2x_options/models/ir_config_parameter.py @@ -13,6 +13,8 @@ def get_web_m2x_options(self): "web_m2x_options.search_more", "web_m2x_options.m2o_dialog", "web_m2x_options.field_limit_entries", + "web_m2x_options.search_mru", + "web_m2x_options.search_mru_max_length", ] values = self.sudo().search_read([["key", "in", opts]], ["key", "value"]) return {res["key"]: res["value"] for res in values} diff --git a/web_m2x_options/readme/USAGE.rst b/web_m2x_options/readme/USAGE.rst index b66170b0fe40..730dd59bfe80 100644 --- a/web_m2x_options/readme/USAGE.rst +++ b/web_m2x_options/readme/USAGE.rst @@ -43,6 +43,13 @@ in the field's options dict Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set) +``search_mru`` *boolean* (Default: ``False``) + + Allows to display to the user the 5 lasts records he selected for a given field. This list will be displayed + when the many2one field is focused and when the user didn't type anything to filter on. The values are stored in + the local storage of the browser and updated after a successful saved of the form. This features only works on form + views at the moment. + ir.config_parameter options ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -73,6 +80,15 @@ If you disable one option, you can enable it for particular field by setting "cr Number of displayed lines on all One2many fields +``web_m2x_options.search_mru`` *boolean* (Default: default value is ``False``) + + Enable MRU for all many2one fields (form view only). + +``web_m2x_options.search_mru_max_length`` *int* + + Changes the length of the records to store and show with MRU feature + + To add these parameters go to Configuration -> Technical -> Parameters -> System Parameters and add new parameters like: - web_m2x_options.create: False @@ -81,6 +97,7 @@ To add these parameters go to Configuration -> Technical -> Parameters -> System - web_m2x_options.limit: 10 - web_m2x_options.search_more: True - web_m2x_options.field_limit_entries: 5 +- web_m2x_options.search_mru: True Example diff --git a/web_m2x_options/static/src/components/form.esm.js b/web_m2x_options/static/src/components/form.esm.js index ecb37d270021..35943b2ed293 100644 --- a/web_m2x_options/static/src/components/form.esm.js +++ b/web_m2x_options/static/src/components/form.esm.js @@ -5,6 +5,11 @@ import { Many2ManyTagsFieldColorEditable, } from "@web/views/fields/many2many_tags/many2many_tags_field"; +import { + isMruGlobalOptionEnabled, + updateMruLocalStorageValues, +} from "@web_m2x_options/utils/mru.esm"; + import {Dialog} from "@web/core/dialog/dialog"; import {FormController} from "@web/views/form/form_controller"; import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog"; @@ -159,6 +164,7 @@ patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", { this._super(...arguments); this.ir_options = Component.env.session.web_m2x_options; }, + /** * @override */ @@ -170,6 +176,7 @@ patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", { searchMore: this.props.searchMore, canCreate: this.props.canCreate, nodeOptions: this.props.nodeOptions, + fieldName: this.props.name, }; }, @@ -401,4 +408,65 @@ patch(FormController.prototype, "web_m2x_options.FormController", { } } }, + + async saveButtonClicked() { + const mruChanges = this.getUpdateMruLocalStorageValues(); + const saved = this._super(...arguments); + updateMruLocalStorageValues(this.props.resModel, mruChanges); + return saved; + }, + + async beforeExecuteActionButton() { + const mruChanges = this.getUpdateMruLocalStorageValues(); + const saved = this._super(...arguments); + updateMruLocalStorageValues(this.props.resModel, mruChanges); + return saved; + }, + + async beforeLeave() { + const mruChanges = this.getUpdateMruLocalStorageValues(); + const saved = this._super(...arguments); + updateMruLocalStorageValues(this.props.resModel, mruChanges); + return saved; + }, + + async onPagerUpdate() { + const mruChanges = this.getUpdateMruLocalStorageValues(); + const saved = this._super(...arguments); + updateMruLocalStorageValues(this.props.resModel, mruChanges); + return saved; + }, + + getUpdateMruLocalStorageValues() { + if (!this.model.root.isDirty) { + return {}; + } + const model = this.model; + const changes = model.__bm__._generateChanges( + model.__bm__.localData[model.root.__bm_handle__], + {changesOnly: true} + ); + const mruChanges = {}; + let enableMru = false; + let nodeOptions = {}; + let fieldInfo = {}; + const activeFields = this.archInfo.activeFields; + Object.keys(changes).forEach(function (key) { + fieldInfo = activeFields[key]; + if ( + Boolean(fieldInfo) && + fieldInfo.FieldComponent.name === "Many2OneField" + ) { + nodeOptions = fieldInfo.options; + enableMru = + nodeOptions.search_mru !== undefined + ? nodeOptions.search_mru + : isMruGlobalOptionEnabled(); + if (enableMru) { + mruChanges[key] = changes[key]; + } + } + }); + return mruChanges; + }, }); diff --git a/web_m2x_options/static/src/components/relational_utils.esm.js b/web_m2x_options/static/src/components/relational_utils.esm.js index 1fbe39eae08a..a1306a08fb41 100644 --- a/web_m2x_options/static/src/components/relational_utils.esm.js +++ b/web_m2x_options/static/src/components/relational_utils.esm.js @@ -1,8 +1,15 @@ /** @odoo-module **/ +import { + getMruKey, + getMruValue, + isMruGlobalOptionEnabled, +} from "@web_m2x_options/utils/mru.esm"; + import {Many2XAutocomplete} from "@web/views/fields/relational_utils"; import {patch} from "@web/core/utils/patch"; import {sprintf} from "@web/core/utils/strings"; + const {Component} = owl; export function is_option_set(option) { @@ -16,6 +23,44 @@ patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", { setup() { this._super(...arguments); this.ir_options = Component.env.session.web_m2x_options; + const searchMruOption = this.props.nodeOptions.search_mru; + this.enableMru = + searchMruOption === undefined + ? isMruGlobalOptionEnabled() + : this.props.nodeOptions.search_mru; + if (this.enableMru) { + this.mruKey = getMruKey(this.env.model.root.resModel, this.props.fieldName); + } + }, + + async loadRecords(request) { + const withMru = this.enableMru && Boolean(!request); + this.lastProm = this.orm.call(this.props.resModel, "name_search", [], { + name: request, + operator: "ilike", + args: this.getLoadRecordsDomain(withMru), + limit: this.props.searchLimit + 1, + context: this.props.context, + }); + const records = await this.lastProm; + + if (withMru) { + const cachedIds = getMruValue(this.mruKey); + records.sort((record1, record2) => { + return cachedIds.indexOf(record1[0]) - cachedIds.indexOf(record2[0]); + }); + } + + return records; + }, + + getLoadRecordsDomain(withMru) { + const domain = this.props.getDomain(); + const mruValue = withMru ? getMruValue(this.mruKey) : false; + if (Boolean(mruValue) && mruValue.length > 0) { + domain.push(["id", "in", mruValue]); + } + return domain; }, async loadOptionsSource(request) { @@ -24,6 +69,7 @@ patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", { } // Add options limit used to change number of selections record // returned. + if (!_.isUndefined(this.ir_options["web_m2x_options.limit"])) { this.props.searchLimit = parseInt( this.ir_options["web_m2x_options.limit"], @@ -41,15 +87,7 @@ patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", { this.field_color = this.props.nodeOptions.field_color; this.colors = this.props.nodeOptions.colors; - this.lastProm = this.orm.call(this.props.resModel, "name_search", [], { - name: request, - operator: "ilike", - args: this.props.getDomain(), - limit: this.props.searchLimit + 1, - context: this.props.context, - }); - const records = await this.lastProm; - + const records = await this.loadRecords(request); var options = records.map((result) => ({ value: result[0], id: result[0], diff --git a/web_m2x_options/static/src/utils/mru.esm.js b/web_m2x_options/static/src/utils/mru.esm.js new file mode 100644 index 000000000000..d6ec248089e6 --- /dev/null +++ b/web_m2x_options/static/src/utils/mru.esm.js @@ -0,0 +1,62 @@ +/** @odoo-module **/ +import {session} from "@web/session"; + +const LOCAL_STORAGE_NAME = "web_m2x_options_mru"; + +export function getMruMaxLength() { + return ( + parseInt(session.web_m2x_options["web_m2x_options.search_mru_max_length"]) || 5 + ); +} + +export function isMruGlobalOptionEnabled() { + return session.web_m2x_options["web_m2x_options.search_mru"] === "True"; +} + +export function getMruKey(modelName, fieldName) { + return session.db + "/" + modelName + "/" + fieldName; +} + +export function getMruStorage() { + let data = localStorage.getItem(LOCAL_STORAGE_NAME); + if (!data) { + return {}; + } + data = JSON.parse(data); + return data; +} + +export function getMruValue(mruKey) { + const data = getMruStorage(); + return mruKey in data ? data[mruKey] : []; +} + +export function setMruValue(mruKey, value) { + const data = getMruStorage(); + data[mruKey] = value; + localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data)); +} + +export function updateMruIds(mruKey, recordId) { + if (!recordId) { + return; + } + const cachedIds = getMruValue(mruKey); + const currentIndex = cachedIds.indexOf(recordId); + if (currentIndex !== -1) { + cachedIds.splice(currentIndex, 1); + } + cachedIds.unshift(recordId); + const maxLength = getMruMaxLength(); + if (cachedIds.length > maxLength) { + cachedIds.splice(maxLength, cachedIds.length - maxLength); + } + setMruValue(mruKey, cachedIds); +} + +export function updateMruLocalStorageValues(modelName, values) { + Object.keys(values).forEach(function (key) { + const mruKey = getMruKey(modelName, key); + updateMruIds(mruKey, values[key]); + }); +}