Skip to content

Commit cfd32ed

Browse files
committed
[ADD] web_m2x_options: display last selected records at first when selecting a value in a many2one before the user types something
1 parent 7c2a4ea commit cfd32ed

File tree

7 files changed

+220
-10
lines changed

7 files changed

+220
-10
lines changed

web_m2x_options/README.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ in the field's options dict
9292

9393
Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set)
9494

95+
``search_mru`` *boolean* (Default: ``False``)
96+
97+
Allows to display to the user the 5 lasts records he selected for a given field. This list will be displayed
98+
when the many2one field is focused and when the user didn't type anything to filter on. The values are stored in
99+
the local storage of the browser and updated after a successful saved of the form. This features only works on form
100+
views at the moment.
101+
95102
ir.config_parameter options
96103
~~~~~~~~~~~~~~~~~~~~~~~~~~~
97104

@@ -122,6 +129,15 @@ If you disable one option, you can enable it for particular field by setting "cr
122129

123130
Number of displayed lines on all One2many fields
124131

132+
``web_m2x_options.search_mru`` *boolean* (Default: default value is ``False``)
133+
134+
Enable MRU for all many2one fields (form view only).
135+
136+
``web_m2x_options.search_mru_max_length`` *int*
137+
138+
Changes the length of the records to store and show with MRU feature
139+
140+
125141
To add these parameters go to Configuration -> Technical -> Parameters -> System Parameters and add new parameters like:
126142

127143
- web_m2x_options.create: False
@@ -130,6 +146,7 @@ To add these parameters go to Configuration -> Technical -> Parameters -> System
130146
- web_m2x_options.limit: 10
131147
- web_m2x_options.search_more: True
132148
- web_m2x_options.field_limit_entries: 5
149+
- web_m2x_options.search_mru: True
133150

134151

135152
Example

web_m2x_options/__manifest__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"website": "https://github.com/OCA/web",
1717
"license": "AGPL-3",
1818
"depends": ["web"],
19-
"assets": {"web.assets_backend": ["web_m2x_options/static/src/components/*"]},
19+
"assets": {
20+
"web.assets_backend": [
21+
"web_m2x_options/static/src/components/*",
22+
"web_m2x_options/static/src/utils/mru.esm.js",
23+
]
24+
},
2025
"installable": True,
2126
}

web_m2x_options/models/ir_config_parameter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ def get_web_m2x_options(self):
1313
"web_m2x_options.search_more",
1414
"web_m2x_options.m2o_dialog",
1515
"web_m2x_options.field_limit_entries",
16+
"web_m2x_options.search_mru",
17+
"web_m2x_options.search_mru_max_length",
1618
]
1719
values = self.sudo().search_read([["key", "in", opts]], ["key", "value"])
1820
return {res["key"]: res["value"] for res in values}

web_m2x_options/readme/USAGE.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ in the field's options dict
4343

4444
Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set)
4545

46+
``search_mru`` *boolean* (Default: ``False``)
47+
48+
Allows to display to the user the 5 lasts records he selected for a given field. This list will be displayed
49+
when the many2one field is focused and when the user didn't type anything to filter on. The values are stored in
50+
the local storage of the browser and updated after a successful saved of the form. This features only works on form
51+
views at the moment.
52+
4653
ir.config_parameter options
4754
~~~~~~~~~~~~~~~~~~~~~~~~~~~
4855

@@ -73,6 +80,15 @@ If you disable one option, you can enable it for particular field by setting "cr
7380

7481
Number of displayed lines on all One2many fields
7582

83+
``web_m2x_options.search_mru`` *boolean* (Default: default value is ``False``)
84+
85+
Enable MRU for all many2one fields (form view only).
86+
87+
``web_m2x_options.search_mru_max_length`` *int*
88+
89+
Changes the length of the records to store and show with MRU feature
90+
91+
7692
To add these parameters go to Configuration -> Technical -> Parameters -> System Parameters and add new parameters like:
7793

7894
- web_m2x_options.create: False
@@ -81,6 +97,7 @@ To add these parameters go to Configuration -> Technical -> Parameters -> System
8197
- web_m2x_options.limit: 10
8298
- web_m2x_options.search_more: True
8399
- web_m2x_options.field_limit_entries: 5
100+
- web_m2x_options.search_mru: True
84101

85102

86103
Example

web_m2x_options/static/src/components/form.esm.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import {
55
Many2ManyTagsFieldColorEditable,
66
} from "@web/views/fields/many2many_tags/many2many_tags_field";
77

8+
import {
9+
isMruGlobalOptionEnabled,
10+
updateMruLocalStorageValues,
11+
} from "@web_m2x_options/utils/mru.esm";
12+
813
import {Dialog} from "@web/core/dialog/dialog";
914
import {FormController} from "@web/views/form/form_controller";
1015
import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
@@ -159,6 +164,7 @@ patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", {
159164
this._super(...arguments);
160165
this.ir_options = Component.env.session.web_m2x_options;
161166
},
167+
162168
/**
163169
* @override
164170
*/
@@ -170,6 +176,7 @@ patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", {
170176
searchMore: this.props.searchMore,
171177
canCreate: this.props.canCreate,
172178
nodeOptions: this.props.nodeOptions,
179+
fieldName: this.props.name,
173180
};
174181
},
175182

@@ -401,4 +408,66 @@ patch(FormController.prototype, "web_m2x_options.FormController", {
401408
}
402409
}
403410
},
411+
412+
async saveButtonClicked() {
413+
const mruChanges = this.getUpdateMruLocalStorageValues();
414+
const saved = this._super(...arguments);
415+
updateMruLocalStorageValues(this.props.resModel, mruChanges);
416+
return saved;
417+
},
418+
419+
async beforeExecuteActionButton() {
420+
const mruChanges = this.getUpdateMruLocalStorageValues();
421+
const saved = this._super(...arguments);
422+
updateMruLocalStorageValues(this.props.resModel, mruChanges);
423+
return saved;
424+
},
425+
426+
async beforeLeave() {
427+
const mruChanges = this.getUpdateMruLocalStorageValues();
428+
const saved = this._super(...arguments);
429+
updateMruLocalStorageValues(this.props.resModel, mruChanges);
430+
return saved;
431+
},
432+
433+
async onPagerUpdate() {
434+
const mruChanges = this.getUpdateMruLocalStorageValues();
435+
const saved = this._super(...arguments);
436+
updateMruLocalStorageValues(this.props.resModel, mruChanges);
437+
return saved;
438+
},
439+
440+
getUpdateMruLocalStorageValues() {
441+
if (!this.model.root.isDirty) {
442+
return {};
443+
}
444+
const model = this.model;
445+
const changes = model.__bm__._generateChanges(
446+
model.__bm__.localData[model.root.__bm_handle__],
447+
{changesOnly: true}
448+
);
449+
const mruChanges = {};
450+
let enableMru = false;
451+
let nodeOptions = {};
452+
let fieldInfo = {};
453+
const activeFields = this.archInfo.activeFields;
454+
Object.keys(changes).forEach(function (key) {
455+
fieldInfo = activeFields[key];
456+
if (
457+
Boolean(fieldInfo) &&
458+
Boolean(fieldInfo.FieldComponent) &&
459+
fieldInfo.FieldComponent.name === "Many2OneField"
460+
) {
461+
nodeOptions = fieldInfo.options;
462+
enableMru =
463+
nodeOptions.search_mru !== undefined
464+
? nodeOptions.search_mru
465+
: isMruGlobalOptionEnabled();
466+
if (enableMru) {
467+
mruChanges[key] = changes[key];
468+
}
469+
}
470+
});
471+
return mruChanges;
472+
},
404473
});

web_m2x_options/static/src/components/relational_utils.esm.js

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
/** @odoo-module **/
22

3+
import {
4+
getMruKey,
5+
getMruValue,
6+
isMruGlobalOptionEnabled,
7+
} from "@web_m2x_options/utils/mru.esm";
8+
39
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
410
import {patch} from "@web/core/utils/patch";
511
import {sprintf} from "@web/core/utils/strings";
12+
613
const {Component} = owl;
714

815
export function is_option_set(option) {
@@ -16,6 +23,44 @@ patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", {
1623
setup() {
1724
this._super(...arguments);
1825
this.ir_options = Component.env.session.web_m2x_options;
26+
const searchMruOption = this.props.nodeOptions.search_mru;
27+
this.enableMru =
28+
searchMruOption === undefined
29+
? isMruGlobalOptionEnabled()
30+
: this.props.nodeOptions.search_mru;
31+
if (this.enableMru) {
32+
this.mruKey = getMruKey(this.env.model.root.resModel, this.props.fieldName);
33+
}
34+
},
35+
36+
async loadRecords(request) {
37+
const withMru = this.enableMru && Boolean(!request);
38+
this.lastProm = this.orm.call(this.props.resModel, "name_search", [], {
39+
name: request,
40+
operator: "ilike",
41+
args: this.getLoadRecordsDomain(withMru),
42+
limit: this.props.searchLimit + 1,
43+
context: this.props.context,
44+
});
45+
const records = await this.lastProm;
46+
47+
if (withMru) {
48+
const cachedIds = getMruValue(this.mruKey);
49+
records.sort((record1, record2) => {
50+
return cachedIds.indexOf(record1[0]) - cachedIds.indexOf(record2[0]);
51+
});
52+
}
53+
54+
return records;
55+
},
56+
57+
getLoadRecordsDomain(withMru) {
58+
const domain = this.props.getDomain();
59+
const mruValue = withMru ? getMruValue(this.mruKey) : false;
60+
if (Boolean(mruValue) && mruValue.length > 0) {
61+
domain.push(["id", "in", mruValue]);
62+
}
63+
return domain;
1964
},
2065

2166
async loadOptionsSource(request) {
@@ -24,6 +69,7 @@ patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", {
2469
}
2570
// Add options limit used to change number of selections record
2671
// returned.
72+
2773
if (!_.isUndefined(this.ir_options["web_m2x_options.limit"])) {
2874
this.props.searchLimit = parseInt(
2975
this.ir_options["web_m2x_options.limit"],
@@ -41,15 +87,7 @@ patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", {
4187
this.field_color = this.props.nodeOptions.field_color;
4288
this.colors = this.props.nodeOptions.colors;
4389

44-
this.lastProm = this.orm.call(this.props.resModel, "name_search", [], {
45-
name: request,
46-
operator: "ilike",
47-
args: this.props.getDomain(),
48-
limit: this.props.searchLimit + 1,
49-
context: this.props.context,
50-
});
51-
const records = await this.lastProm;
52-
90+
const records = await this.loadRecords(request);
5391
var options = records.map((result) => ({
5492
value: result[0],
5593
id: result[0],
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/** @odoo-module **/
2+
import {session} from "@web/session";
3+
4+
const LOCAL_STORAGE_NAME = "web_m2x_options_mru";
5+
6+
export function getMruMaxLength() {
7+
return (
8+
parseInt(session.web_m2x_options["web_m2x_options.search_mru_max_length"]) || 5
9+
);
10+
}
11+
12+
export function isMruGlobalOptionEnabled() {
13+
return session.web_m2x_options["web_m2x_options.search_mru"] === "True";
14+
}
15+
16+
export function getMruKey(modelName, fieldName) {
17+
return session.db + "/" + modelName + "/" + fieldName;
18+
}
19+
20+
export function getMruStorage() {
21+
let data = localStorage.getItem(LOCAL_STORAGE_NAME);
22+
if (!data) {
23+
return {};
24+
}
25+
data = JSON.parse(data);
26+
return data;
27+
}
28+
29+
export function getMruValue(mruKey) {
30+
const data = getMruStorage();
31+
return mruKey in data ? data[mruKey] : [];
32+
}
33+
34+
export function setMruValue(mruKey, value) {
35+
const data = getMruStorage();
36+
data[mruKey] = value;
37+
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
38+
}
39+
40+
export function updateMruIds(mruKey, recordId) {
41+
if (!recordId) {
42+
return;
43+
}
44+
const cachedIds = getMruValue(mruKey);
45+
const currentIndex = cachedIds.indexOf(recordId);
46+
if (currentIndex !== -1) {
47+
cachedIds.splice(currentIndex, 1);
48+
}
49+
cachedIds.unshift(recordId);
50+
const maxLength = getMruMaxLength();
51+
if (cachedIds.length > maxLength) {
52+
cachedIds.splice(maxLength, cachedIds.length - maxLength);
53+
}
54+
setMruValue(mruKey, cachedIds);
55+
}
56+
57+
export function updateMruLocalStorageValues(modelName, values) {
58+
Object.keys(values).forEach(function (key) {
59+
const mruKey = getMruKey(modelName, key);
60+
updateMruIds(mruKey, values[key]);
61+
});
62+
}

0 commit comments

Comments
 (0)