From b803f95772dbb3a77577aac4c82ef4a0fea60b60 Mon Sep 17 00:00:00 2001 From: Diep Huu Hoang Date: Tue, 28 Mar 2023 12:48:05 +0700 Subject: [PATCH] [16.0][MIG] web_m2x_options: Migration to 16.0 --- .../odoo/addons/web_m2x_options | 1 + setup/web_m2x_options/setup.py | 6 + web_m2x_options/README.rst | 5 +- web_m2x_options/__manifest__.py | 10 +- web_m2x_options/readme/CONTRIBUTORS.rst | 2 + web_m2x_options/readme/CREDITS.rst | 1 + .../static/src/components/base.xml | 46 ++ .../static/src/components/form.esm.js | 418 +++++++++++++++ .../src/components/relational_utils.esm.js | 231 +++++++++ web_m2x_options/static/src/js/form.js | 489 ------------------ web_m2x_options/static/src/js/ir_options.js | 12 - web_m2x_options/static/src/xml/base.xml | 12 - 12 files changed, 710 insertions(+), 523 deletions(-) create mode 120000 setup/web_m2x_options/odoo/addons/web_m2x_options create mode 100644 setup/web_m2x_options/setup.py create mode 100644 web_m2x_options/readme/CREDITS.rst create mode 100644 web_m2x_options/static/src/components/base.xml create mode 100644 web_m2x_options/static/src/components/form.esm.js create mode 100644 web_m2x_options/static/src/components/relational_utils.esm.js delete mode 100644 web_m2x_options/static/src/js/form.js delete mode 100644 web_m2x_options/static/src/js/ir_options.js delete mode 100644 web_m2x_options/static/src/xml/base.xml diff --git a/setup/web_m2x_options/odoo/addons/web_m2x_options b/setup/web_m2x_options/odoo/addons/web_m2x_options new file mode 120000 index 000000000..16ed8b7f0 --- /dev/null +++ b/setup/web_m2x_options/odoo/addons/web_m2x_options @@ -0,0 +1 @@ +../../../../web_m2x_options \ No newline at end of file diff --git a/setup/web_m2x_options/setup.py b/setup/web_m2x_options/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_m2x_options/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_m2x_options/README.rst b/web_m2x_options/README.rst index 66002974a..6133b7f13 100644 --- a/web_m2x_options/README.rst +++ b/web_m2x_options/README.rst @@ -23,7 +23,7 @@ web_m2x_options :target: https://runbot.odoo-community.org/runbot/162/15.0 :alt: Try me on Runbot -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This modules modifies "many2one" and "many2manytags" form widgets so as to add some new display control options. @@ -85,10 +85,11 @@ in the field's options dict Makes many2many_tags and one2many rows buttons that open the linked resource -``no_color_picker`` *boolean* (Default: ``False``) +``no_color_picker`` *boolean* (Default: ``False``) (obselete: use native option: ``no_edit_color`` instead) Deactivates the color picker on many2many_tags buttons to do nothing (ignored if open is set) + ir.config_parameter options ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/web_m2x_options/__manifest__.py b/web_m2x_options/__manifest__.py index af30beb4b..9da81d7df 100644 --- a/web_m2x_options/__manifest__.py +++ b/web_m2x_options/__manifest__.py @@ -6,7 +6,7 @@ { "name": "web_m2x_options", - "version": "15.0.1.1.0", + "version": "16.0.1.1.0", "category": "Web", "author": "initOS GmbH," "ACSONE SA/NV, " @@ -16,12 +16,6 @@ "website": "https://github.com/OCA/web", "license": "AGPL-3", "depends": ["web"], - "assets": { - "web.assets_backend": [ - "web_m2x_options/static/src/js/form.js", - "web_m2x_options/static/src/js/ir_options.js", - ], - "web.assets_qweb": ["web_m2x_options/static/src/xml/base.xml"], - }, + "assets": {"web.assets_backend": ["web_m2x_options/static/src/components/*"]}, "installable": True, } diff --git a/web_m2x_options/readme/CONTRIBUTORS.rst b/web_m2x_options/readme/CONTRIBUTORS.rst index c42c7afe7..e7ae138b5 100644 --- a/web_m2x_options/readme/CONTRIBUTORS.rst +++ b/web_m2x_options/readme/CONTRIBUTORS.rst @@ -12,3 +12,5 @@ * Carlos Roca * Bhavesh Odedra * Dhara Solanki (http://www.initos.com) +* `Trobz `_: + * Hoang Diep diff --git a/web_m2x_options/readme/CREDITS.rst b/web_m2x_options/readme/CREDITS.rst new file mode 100644 index 000000000..70bc307ad --- /dev/null +++ b/web_m2x_options/readme/CREDITS.rst @@ -0,0 +1 @@ +The migration of this module from 15.0 to 16.0 was financially supported by Camptocamp diff --git a/web_m2x_options/static/src/components/base.xml b/web_m2x_options/static/src/components/base.xml new file mode 100644 index 000000000..d8719f7d0 --- /dev/null +++ b/web_m2x_options/static/src/components/base.xml @@ -0,0 +1,46 @@ + + + + + + + + {{ option.style }} + + + + + + props.nodeOptions + + + + + +
+ You are creating a new as a new , are you sure it does not exist yet? +
+ + + + + +
+
+
diff --git a/web_m2x_options/static/src/components/form.esm.js b/web_m2x_options/static/src/components/form.esm.js new file mode 100644 index 000000000..2b2923105 --- /dev/null +++ b/web_m2x_options/static/src/components/form.esm.js @@ -0,0 +1,418 @@ +/** @odoo-module **/ + +import { + Many2ManyTagsField, + Many2ManyTagsFieldColorEditable, +} from "@web/views/fields/many2many_tags/many2many_tags_field"; + +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"; +import {Many2OneAvatarField} from "@web/views/fields/many2one_avatar/many2one_avatar_field"; +import {Many2OneBarcodeField} from "@web/views/fields/many2one_barcode/many2one_barcode_field"; +import {Many2OneField} from "@web/views/fields/many2one/many2one_field"; +import {ReferenceField} from "@web/views/fields/reference/reference_field"; +import {X2ManyField} from "@web/views/fields/x2many/x2many_field"; +import {isX2Many} from "@web/views/utils"; +import {is_option_set} from "@web_m2x_options/components/relational_utils.esm"; +import {patch} from "@web/core/utils/patch"; +import {sprintf} from "@web/core/utils/strings"; +import {useService} from "@web/core/utils/hooks"; + +const {Component, onWillStart} = owl; + +/** + * Patch Many2ManyTagsField + **/ +patch(Many2ManyTagsField.prototype, "web_m2x_options.Many2ManyTagsField", { + setup() { + this._super(...arguments); + this.actionService = useService("action"); + }, + /** + * @override + */ + getTagProps(record) { + const props = this._super(...arguments); + props.onClick = (ev) => this.onMany2ManyBadgeClick(ev, record); + return props; + }, + async onMany2ManyBadgeClick(event, record) { + var self = this; + if (self.props.open) { + var context = self.context; + var id = record.data.id; + if (self.props.readonly) { + event.preventDefault(); + event.stopPropagation(); + const action = await self.orm.call( + self.props.relation, + "get_formview_action", + [[id]], + {context: context} + ); + self.actionService.doAction(action); + } else { + const view_id = await self.orm.call( + self.props.relation, + "get_formview_id", + [[id]], + {context: context} + ); + + const write_access = await self.orm.call( + self.props.relation, + "check_access_rights", + [], + {operation: "write", raise_exception: false} + ); + var can_write = self.props.canWrite; + self.dialog.add(FormViewDialog, { + resModel: self.props.relation, + resId: id, + context: context, + title: self.env._t("Open: ") + self.string, + viewId: view_id, + mode: !can_write || !write_access ? "readonly" : "edit", + onRecordSaved: () => self.props.value.model.load(), + }); + } + } + }, +}); + +Many2ManyTagsField.props = { + ...Many2ManyTagsField.props, + open: {type: Boolean, optional: true}, + canWrite: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +const Many2ManyTagsFieldExtractProps = Many2ManyTagsField.extractProps; +Many2ManyTagsField.extractProps = ({attrs, field}) => { + const canOpen = Boolean(attrs.options.open); + const canWrite = attrs.can_write && Boolean(JSON.parse(attrs.can_write)); + return Object.assign(Many2ManyTagsFieldExtractProps({attrs, field}), { + open: canOpen, + canWrite: canWrite, + nodeOptions: attrs.options, + }); +}; + +/** + * Many2ManyTagsFieldColorEditable + **/ +patch( + Many2ManyTagsFieldColorEditable.prototype, + "web_m2x_options.Many2ManyTagsFieldColorEditable", + { + async onBadgeClick(event, record) { + if (this.props.canEditColor && !this.props.open) { + this._super(...arguments); + } + if (this.props.open) { + Many2ManyTagsField.prototype.onMany2ManyBadgeClick.bind(this)( + event, + record + ); + } + }, + } +); + +Many2ManyTagsFieldColorEditable.props = { + ...Many2ManyTagsFieldColorEditable.props, + open: {type: Boolean, optional: true}, + canWrite: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * CreateConfirmationDialog + * New customized component for Many2One Field + **/ + +class CreateConfirmationDialog extends Component { + get title() { + return sprintf(this.env._t("New: %s"), this.props.name); + } + + async onCreate() { + await this.props.create(); + this.props.close(); + } + async onCreateEdit() { + await this.props.createEdit(); + this.props.close(); + } +} +CreateConfirmationDialog.components = {Dialog}; +CreateConfirmationDialog.template = + "web_m2x_options.Many2OneField.CreateConfirmationDialog"; + +/** + * Many2OneField + **/ + +patch(Many2OneField.prototype, "web_m2x_options.Many2OneField", { + setup() { + this._super(...arguments); + const ormService = useService("orm"); + this.user_context = Component.env.session.user_context; + onWillStart(async () => { + this.ir_options = await ormService.call( + "ir.config_parameter", + "get_web_m2x_options", + [], + {context: this.user_context} + ); + }); + }, + /** + * @override + */ + get Many2XAutocompleteProps() { + const props = this._super(...arguments); + return { + ...props, + searchLimit: this.props.searchLimit, + searchMore: this.props.searchMore, + canCreate: this.props.canCreate, + nodeOptions: this.props.nodeOptions, + }; + }, + + async openConfirmationDialog(request) { + var m2o_dialog_opt = + is_option_set(this.props.nodeOptions.m2o_dialog) || + (_.isUndefined(this.props.nodeOptions.m2o_dialog) && + is_option_set(this.ir_options["web_m2x_options.m2o_dialog"])) || + (_.isUndefined(this.props.nodeOptions.m2o_dialog) && + _.isUndefined(this.ir_options["web_m2x_options.m2o_dialog"])); + if (this.props.canCreate && this.state.isFloating && m2o_dialog_opt) { + return new Promise((resolve, reject) => { + this.addDialog(CreateConfirmationDialog, { + value: request, + name: this.props.string, + create: async () => { + try { + await this.quickCreate(request); + resolve(); + } catch (e) { + reject(e); + } + }, + createEdit: async () => { + try { + await this.quickCreate(request); + await this.props.record.model.load(); + this.openMany2X({ + resId: this.props.value[0], + context: this.user_context, + }); + resolve(); + } catch (e) { + reject(e); + } + }, + }); + }); + } + }, +}); + +const Many2OneFieldExtractProps = Many2OneField.extractProps; +Many2OneField.extractProps = ({attrs, field}) => { + return Object.assign(Many2OneFieldExtractProps({attrs, field}), { + searchLimit: attrs.options.limit, + searchMore: attrs.options.search_more, + nodeOptions: attrs.options, + }); +}; + +Many2OneField.props = { + ...Many2OneField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override ReferenceField + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + */ + +ReferenceField.props = { + ...ReferenceField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override Many2OneBarcodeField + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + */ + +Many2OneBarcodeField.props = { + ...Many2OneBarcodeField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override Many2OneAvatarField + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + */ +Many2OneAvatarField.props = { + ...Many2OneAvatarField.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, +}; + +/** + * FIXME: find better way to extend props in Many2OneField + * Override mailing_m2o_filter + * Since extracted/added props: nodeOptions and searchMore into Many2OneField props + * and this component inherited props from Many2OneField + * So, must override props here to avoid constraint validateProps (props schema) in owl core + * This component is in module mass_mailing as optional module, + * So need to import dynamic way + */ +try { + (async () => { + // Make sure component mailing_m2o_filter in mass mailing module loaded + const installed_mass_mailing = await odoo.ready( + "@mass_mailing/js/mailing_m2o_filter" + ); + if (installed_mass_mailing) { + var FieldMany2OneMailingFilter = await odoo.runtimeImport( + "@mass_mailing/js/mailing_m2o_filter" + ); + FieldMany2OneMailingFilter.props = { + ...FieldMany2OneMailingFilter.props, + searchMore: {type: Boolean, optional: true}, + nodeOptions: {type: Object, optional: true}, + }; + } + })(); +} catch { + console.log( + "Ignore overriding props of component mailing_m2o_filter since the module is not installed" + ); +} + +/** + * X2ManyField + **/ +patch(X2ManyField.prototype, "web_m2x_options.X2ManyField", { + /** + * @override + */ + async openRecord(record) { + var self = this; + var open = this.props.open; + if (open && self.props.readonly) { + var res_id = record.data.id; + const action = await self.env.model.orm.call( + self.props.value.resModel, + "get_formview_action", + [[res_id]] + ); + return self.env.model.actionService.doAction(action); + } + return this._super.apply(this, arguments); + }, +}); + +const X2ManyFieldExtractProps = X2ManyField.extractProps; +X2ManyField.extractProps = ({attrs}) => { + const canOpen = Boolean(attrs.options.open); + return Object.assign(X2ManyFieldExtractProps({attrs}), { + open: canOpen, + }); +}; + +X2ManyField.props = { + ...X2ManyField.props, + open: {type: Boolean, optional: true}, +}; + +/** + * FormController + **/ +patch(FormController.prototype, "web_m2x_options.FormController", { + /** + * @override + */ + setup() { + var self = this; + this._super(...arguments); + + /** Due to problem of 2 onWillStart in native web core + * (see: https://github.com/odoo/odoo/blob/16.0/addons/web/static/src/views/model.js#L142) + * do the trick to override beforeLoadResolver here to customize viewLimit + */ + this.superBeforeLoadResolver = this.beforeLoadResolver; + this.beforeLoadResolver = async () => { + await self._setSubViewLimit(); + self.superBeforeLoadResolver(); + }; + }, + /** + * @override + * add more method to add subview limit on formview + */ + async _setSubViewLimit() { + const ir_options = await this.model.orm.call( + "ir.config_parameter", + "get_web_m2x_options", + [], + {context: this.user_context} + ); + + const activeFields = this.archInfo.activeFields, + fields = this.props.fields, + isSmall = this.user; + + var limit = ir_options["web_m2x_options.field_limit_entries"]; + if (!_.isUndefined(limit)) { + limit = parseInt(limit, 10); + } + + for (const fieldName in activeFields) { + const field = fields[fieldName]; + if (!isX2Many(field)) { + // What follows only concerns x2many fields + continue; + } + const fieldInfo = activeFields[fieldName]; + if (fieldInfo.modifiers.invisible === true) { + // No need to fetch the sub view if the field is always invisible + continue; + } + + if (!fieldInfo.FieldComponent.useSubView) { + // The FieldComponent used to render the field doesn't need a sub view + continue; + } + + let viewType = fieldInfo.viewMode || "list,kanban"; + viewType = viewType.replace("tree", "list"); + if (viewType.includes(",")) { + viewType = isSmall ? "kanban" : "list"; + } + fieldInfo.viewMode = viewType; + if (fieldInfo.views[viewType] && limit) { + fieldInfo.views[viewType].limit = limit; + } + } + }, +}); diff --git a/web_m2x_options/static/src/components/relational_utils.esm.js b/web_m2x_options/static/src/components/relational_utils.esm.js new file mode 100644 index 000000000..645fa1bd9 --- /dev/null +++ b/web_m2x_options/static/src/components/relational_utils.esm.js @@ -0,0 +1,231 @@ +/** @odoo-module **/ + +import {Many2XAutocomplete} from "@web/views/fields/relational_utils"; +import {patch} from "@web/core/utils/patch"; +import {sprintf} from "@web/core/utils/strings"; +import {useService} from "@web/core/utils/hooks"; +const {Component, onWillStart} = owl; + +export function is_option_set(option) { + if (_.isUndefined(option)) return false; + if (typeof option === "string") return option === "true" || option === "True"; + if (typeof option === "boolean") return option; + return false; +} + +patch(Many2XAutocomplete.prototype, "web_m2x_options.Many2XAutocomplete", { + setup() { + this._super(...arguments); + const ormService = useService("orm"); + this.user_context = Component.env.session.user_context; + onWillStart(async () => { + this.ir_options = await ormService.call( + "ir.config_parameter", + "get_web_m2x_options", + [], + {context: this.user_context} + ); + }); + }, + + async loadOptionsSource(request) { + if (this.lastProm) { + this.lastProm.abort(false); + } + // 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"], + 10 + ); + this.limit = this.props.searchLimit; + } + + if (typeof this.props.nodeOptions.limit === "number") { + this.props.searchLimit = this.props.nodeOptions.limit; + this.limit = this.props.searchLimit; + } + + // Add options field_color and colors to color item(s) depending on field_color value + 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; + + var options = records.map((result) => ({ + value: result[0], + id: result[0], + label: result[1].split("\n")[0], + })); + + // Limit results if there is a custom limit options + if (this.limit) { + options = options.slice(0, this.props.searchLimit); + } + + // Search result value colors + if (this.colors && this.field_color) { + var value_ids = options.map((result) => result.value); + const objects = await this.orm.call( + this.props.resModel, + "search_read", + [], + { + domain: [["id", "in", value_ids]], + fields: [this.field_color], + } + ); + for (var index in objects) { + for (var index_value in options) { + if (options[index_value].id === objects[index].id) { + // Find value in values by comparing ids + var option = options[index_value]; + // Find color with field value as key + var color = + this.colors[objects[index][this.field_color]] || "black"; + option.style = "color:" + color; + break; + } + } + } + } + + // Quick create + // Note: Create should be before `search_more` (reserve native order) + // One more reason: when calling `onInputBlur`, native select the first option (activeSourceOption) + // which triggers m2o_dialog if m2o_dialog=true + var create_enabled = + this.props.quickCreate && !this.props.nodeOptions.no_create; + + var raw_result = _.map(records, function (x) { + return x[1]; + }); + var quick_create = is_option_set(this.props.nodeOptions.create), + quick_create_undef = _.isUndefined(this.props.nodeOptions.create), + m2x_create_undef = _.isUndefined(this.ir_options["web_m2x_options.create"]), + m2x_create = is_option_set(this.ir_options["web_m2x_options.create"]); + var show_create = + (!this.props.nodeOptions && (m2x_create_undef || m2x_create)) || + (this.props.nodeOptions && + (quick_create || + (quick_create_undef && (m2x_create_undef || m2x_create)))); + if ( + create_enabled && + !this.props.nodeOptions.no_quick_create && + request.length > 0 && + !_.contains(raw_result, request) && + show_create + ) { + options.push({ + label: sprintf(this.env._t(`Create "%s"`), request), + classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create", + action: async (params) => { + try { + await this.props.quickCreate(request, params); + } catch { + const context = this.getCreationContext(request); + return this.openMany2X({context}); + } + }, + }); + } + + // Search more... + // Resolution order: + // 1- check if "search_more" is set locally in node's options + // 2- if set locally, apply its value + // 3- if not set locally, check if it's set globally via ir.config_parameter + // 4- if set globally, apply its value + // 5- if not set globally either, check if returned values are more than node's limit + var search_more = false; + if (!_.isUndefined(this.props.nodeOptions.search_more)) { + search_more = is_option_set(this.props.nodeOptions.search_more); + } else if (!_.isUndefined(this.ir_options["web_m2x_options.search_more"])) { + search_more = is_option_set(this.ir_options["web_m2x_options.search_more"]); + } else { + search_more = + !this.props.noSearchMore && this.props.searchLimit < records.length; + } + if (search_more) { + options.push({ + label: this.env._t("Search More..."), + action: this.onSearchMore.bind(this, request), + classList: "o_m2o_dropdown_option o_m2o_dropdown_option_search_more", + }); + } + + // Create and Edit + const canCreateEdit = + "createEdit" in this.activeActions + ? this.activeActions.createEdit + : this.activeActions.create; + if ( + !request.length && + !this.props.value && + (this.props.quickCreate || canCreateEdit) + ) { + options.push({ + label: this.env._t("Start typing..."), + classList: "o_m2o_start_typing", + unselectable: true, + }); + } + + // Create and edit ... + var create_edit = + is_option_set(this.props.nodeOptions.create) || + is_option_set(this.props.nodeOptions.create_edit), + create_edit_undef = + _.isUndefined(this.props.nodeOptions.create) && + _.isUndefined(this.props.nodeOptions.create_edit), + m2x_create_edit_undef = _.isUndefined( + this.ir_options["web_m2x_options.create_edit"] + ), + m2x_create_edit = is_option_set( + this.ir_options["web_m2x_options.create_edit"] + ); + var show_create_edit = + (!this.props.nodeOptions && (m2x_create_edit_undef || m2x_create_edit)) || + (this.props.nodeOptions && + (create_edit || + (create_edit_undef && (m2x_create_edit_undef || m2x_create_edit)))); + if ( + create_enabled && + !this.props.nodeOptions.no_create_edit && + show_create_edit && + request.length && + canCreateEdit + ) { + const context = this.getCreationContext(request); + options.push({ + label: this.env._t("Create and edit..."), + classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create_edit", + action: () => this.openMany2X({context}), + }); + } + + // No records + if (!records.length && !this.activeActions.create) { + options.push({ + label: this.env._t("No records"), + classList: "o_m2o_no_result", + unselectable: true, + }); + } + + return options; + }, +}); + +Many2XAutocomplete.defaultProps = { + ...Many2XAutocomplete.defaultProps, + nodeOptions: {}, +}; diff --git a/web_m2x_options/static/src/js/form.js b/web_m2x_options/static/src/js/form.js deleted file mode 100644 index 46359c4bf..000000000 --- a/web_m2x_options/static/src/js/form.js +++ /dev/null @@ -1,489 +0,0 @@ -/* Copyright 2016 0k.io,ACSONE SA/NV - * * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ - -odoo.define("web_m2x_options.web_m2x_options", function (require) { - "use strict"; - var core = require("web.core"), - data = require("web.data"), - Dialog = require("web.Dialog"), - FormView = require("web.FormView"), - view_dialogs = require("web.view_dialogs"), - relational_fields = require("web.relational_fields"), - ir_options = require("web_m2x_options.ir_options"); - - var _t = core._t, - FieldMany2ManyTags = relational_fields.FieldMany2ManyTags, - FieldMany2One = relational_fields.FieldMany2One, - FieldOne2Many = relational_fields.FieldOne2Many, - FormFieldMany2ManyTags = relational_fields.FormFieldMany2ManyTags; - - function is_option_set(option) { - if (_.isUndefined(option)) return false; - if (typeof option === "string") return option === "true" || option === "True"; - if (typeof option === "boolean") return option; - return false; - } - - var M2ODialog = Dialog.extend({ - template: "M2ODialog", - init: function (parent, name, value) { - this.name = name; - this.value = value; - this._super(parent, { - title: _.str.sprintf(_t("Create a %s"), this.name), - size: "medium", - buttons: [ - { - text: _t("Create"), - classes: "btn-primary", - click: function () { - if (this.$("input").val()) { - this.trigger_up("quick_create", { - value: this.$("input").val(), - }); - this.close(true); - } else { - this.$("input").focus(); - } - }, - }, - { - text: _t("Create and edit"), - classes: "btn-primary", - close: true, - click: function () { - this.trigger_up("search_create_popup", { - view_type: "form", - value: this.$("input").val(), - }); - }, - }, - { - text: _t("Cancel"), - close: true, - }, - ], - }); - }, - start: function () { - this.$("p").text( - _.str.sprintf( - _t( - "You are creating a new %s, are you sure it does not exist yet?" - ), - this.name - ) - ); - this.$("input").val(this.value); - }, - /** - * @override - * @param {Boolean} isSet - */ - close: function (isSet) { - this.isSet = isSet; - this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - if (!this.isSet) { - this.trigger_up("closed_unset"); - } - this._super.apply(this, arguments); - }, - }); - - FieldMany2One.include({ - _onInputFocusout: function () { - var m2o_dialog_opt = - is_option_set(this.nodeOptions.m2o_dialog) || - (_.isUndefined(this.nodeOptions.m2o_dialog) && - is_option_set(ir_options["web_m2x_options.m2o_dialog"])) || - (_.isUndefined(this.nodeOptions.m2o_dialog) && - _.isUndefined(ir_options["web_m2x_options.m2o_dialog"])); - if (this.can_create && this.floating && m2o_dialog_opt) { - new M2ODialog(this, this.string, this.$input.val()).open(); - } - }, - - _search: function (search_val) { - var self = this; - - var def = new Promise((resolve) => { - // Add options limit used to change number of selections record - // returned. - if (!_.isUndefined(ir_options["web_m2x_options.limit"])) { - this.limit = parseInt(ir_options["web_m2x_options.limit"], 10); - } - - if (typeof self.nodeOptions.limit === "number") { - self.limit = self.nodeOptions.limit; - } - - // Add options field_color and colors to color item(s) depending on field_color value - self.field_color = self.nodeOptions.field_color; - self.colors = self.nodeOptions.colors; - - var context = self.record.getContext(self.recordParams); - var domain = self.record.getDomain(self.recordParams); - - var blacklisted_ids = self._getSearchBlacklist(); - if (blacklisted_ids.length > 0) { - domain.push(["id", "not in", blacklisted_ids]); - } - - self._rpc({ - model: self.field.relation, - method: "name_search", - kwargs: { - name: search_val, - args: domain, - operator: "ilike", - limit: self.limit + 1, - context: context, - }, - }).then((result) => { - // Possible selections for the m2o - var values = _.map(result, (x) => { - x[1] = self._getDisplayName(x[1]); - return { - label: - _.str.escapeHTML(x[1].trim()) || data.noDisplayContent, - value: x[1], - name: x[1], - id: x[0], - }; - }); - - // Search result value colors - if (self.colors && self.field_color) { - var value_ids = []; - for (var val_index in values) { - value_ids.push(values[val_index].id); - } - self._rpc({ - model: self.field.relation, - method: "search_read", - fields: [self.field_color], - domain: [["id", "in", value_ids]], - }).then((objects) => { - for (var index in objects) { - for (var index_value in values) { - if (values[index_value].id === objects[index].id) { - // Find value in values by comparing ids - var value = values[index_value]; - // Find color with field value as key - var color = - self.colors[ - objects[index][self.field_color] - ] || "black"; - value.label = - '' + - value.label + - ""; - break; - } - } - } - resolve(values); - }); - } - - // Search more... - // Resolution order: - // 1- check if "search_more" is set locally in node's options - // 2- if set locally, apply its value - // 3- if not set locally, check if it's set globally via ir.config_parameter - // 4- if set globally, apply its value - // 5- if not set globally either, check if returned values are more than node's limit - if (!_.isUndefined(self.nodeOptions.search_more)) { - var search_more = is_option_set(self.nodeOptions.search_more); - } else if ( - !_.isUndefined(ir_options["web_m2x_options.search_more"]) - ) { - var search_more = is_option_set( - ir_options["web_m2x_options.search_more"] - ); - } else { - var search_more = values.length > self.limit; - } - - if (search_more) { - values = values.slice(0, self.limit); - values.push({ - label: _t("Search More..."), - action: function () { - var prom = []; - if (search_val !== "") { - prom = self._rpc({ - model: self.field.relation, - method: "name_search", - kwargs: { - name: search_val, - args: domain, - operator: "ilike", - limit: self.SEARCH_MORE_LIMIT, - context: context, - }, - }); - } - Promise.resolve(prom).then(function (results) { - var dynamicFilters = []; - if (results) { - var ids = _.map(results, function (x) { - return x[0]; - }); - if (search_val) { - dynamicFilters = [ - { - description: _.str.sprintf( - _t("Quick search: %s"), - search_val - ), - domain: [["id", "in", ids]], - }, - ]; - } else { - dynamicFilters = []; - } - } - self._searchCreatePopup( - "search", - false, - {}, - dynamicFilters - ); - }); - }, - classname: "o_m2o_dropdown_option", - }); - } - - var create_enabled = self.can_create && !self.nodeOptions.no_create; - // Quick create - var raw_result = _.map(result, function (x) { - return x[1]; - }); - var quick_create = is_option_set(self.nodeOptions.create), - quick_create_undef = _.isUndefined(self.nodeOptions.create), - m2x_create_undef = _.isUndefined( - ir_options["web_m2x_options.create"] - ), - m2x_create = is_option_set( - ir_options["web_m2x_options.create"] - ); - var show_create = - (!self.nodeOptions && (m2x_create_undef || m2x_create)) || - (self.nodeOptions && - (quick_create || - (quick_create_undef && - (m2x_create_undef || m2x_create)))); - if ( - create_enabled && - !self.nodeOptions.no_quick_create && - search_val.length > 0 && - !_.contains(raw_result, search_val) && - show_create - ) { - values.push({ - label: _.str.sprintf( - _t('Create "%s"'), - $("").text(search_val).html() - ), - action: self._quickCreate.bind(self, search_val), - classname: "o_m2o_dropdown_option", - }); - } - // Create and edit ... - - var create_edit = - is_option_set(self.nodeOptions.create) || - is_option_set(self.nodeOptions.create_edit), - create_edit_undef = - _.isUndefined(self.nodeOptions.create) && - _.isUndefined(self.nodeOptions.create_edit), - m2x_create_edit_undef = _.isUndefined( - ir_options["web_m2x_options.create_edit"] - ), - m2x_create_edit = is_option_set( - ir_options["web_m2x_options.create_edit"] - ); - var show_create_edit = - (!self.nodeOptions && - (m2x_create_edit_undef || m2x_create_edit)) || - (self.nodeOptions && - (create_edit || - (create_edit_undef && - (m2x_create_edit_undef || m2x_create_edit)))); - if ( - create_enabled && - !self.nodeOptions.no_create_edit && - show_create_edit - ) { - var createAndEditAction = function () { - // Clear the value in case the user clicks on discard - self.$("input").val(""); - return self._searchCreatePopup( - "form", - false, - self._createContext(search_val) - ); - }; - values.push({ - label: _t("Create and Edit..."), - action: createAndEditAction, - classname: "o_m2o_dropdown_option", - }); - } else if (values.length === 0) { - values.push({ - label: _t("No results to show..."), - }); - } - // Check if colors specified to wait for RPC - if (!(self.field_color && self.colors)) { - resolve(values); - } - }); - }); - this.orderer.add(def); - - // Add options limit used to change number of selections record - // returned. - if (!_.isUndefined(ir_options["web_m2x_options.limit"])) { - this.limit = parseInt(ir_options["web_m2x_options.limit"], 10); - } - - if (typeof this.nodeOptions.limit === "number") { - this.limit = this.nodeOptions.limit; - } - - return def; - }, - }); - - FieldMany2ManyTags.include({ - events: _.extend({}, FieldMany2ManyTags.prototype.events, { - "click .badge": "_onOpenBadge", - }), - - _onDeleteTag: function (event) { - var result = this._super.apply(this, arguments); - event.stopPropagation(); - return result; - }, - - _onOpenBadge: function (event) { - var self = this; - var open = self.nodeOptions && is_option_set(self.nodeOptions.open); - if (open) { - var context = self.record.getContext(self.recordParams); - var id = parseInt($(event.currentTarget).data("id"), 10); - - if (self.mode === "readonly") { - event.preventDefault(); - event.stopPropagation(); - self._rpc({ - model: self.field.relation, - method: "get_formview_action", - args: [[id]], - context: context, - }).then(function (action) { - self.trigger_up("do_action", {action: action}); - }); - } else { - $.when( - self._rpc({ - model: self.field.relation, - method: "get_formview_id", - args: [[id]], - context: context, - }), - self._rpc({ - model: self.field.relation, - method: "check_access_rights", - kwargs: {operation: "write", raise_exception: false}, - }) - ).then(function (view_id, write_access) { - var can_write = - "can_write" in self.attrs - ? JSON.parse(self.attrs.can_write) - : true; - new view_dialogs.FormViewDialog(self, { - res_model: self.field.relation, - res_id: id, - context: context, - title: _t("Open: ") + self.string, - view_id: view_id, - readonly: !can_write || !write_access, - on_saved: function (record, changed) { - if (changed) { - self._setValue(self.value.data, { - forceChange: true, - }); - self.trigger_up("reload", {db_id: self.value.id}); - } - }, - }).open(); - }); - } - } - }, - }); - - FieldOne2Many.include({ - _onOpenRecord: function (ev) { - var self = this; - var open = this.nodeOptions.open; - if (open && self.mode === "readonly") { - ev.stopPropagation(); - var id = ev.data.id; - var res_id = self.record.data[self.name].data.filter( - (line) => line.id === id - )[0].res_id; - self._rpc({ - model: self.field.relation, - method: "get_formview_action", - args: [[res_id]], - }).then(function (action) { - return self.do_action(action); - }); - } else { - return this._super.apply(this, arguments); - } - }, - }); - - FormFieldMany2ManyTags.include({ - events: _.extend({}, FormFieldMany2ManyTags.prototype.events, { - "click .badge": "_onOpenBadge", - }), - - _onOpenBadge: function (event) { - var open = is_option_set(this.nodeOptions.open); - var no_color_picker = is_option_set(this.nodeOptions.no_color_picker); - this._super.apply(this, arguments); - if (!open && !no_color_picker) { - this._onOpenColorPicker(event); - } else { - event.preventDefault(); - event.stopPropagation(); - } - }, - }); - - // Extending class to allow change the limit of o2m registry entries using the - // system parameter "web_m2x_options.field_limit_entries". - FormView.include({ - _setSubViewLimit: function (attrs) { - this._super(attrs); - var limit = ir_options["web_m2x_options.field_limit_entries"]; - if (!_.isUndefined(limit)) { - attrs.limit = parseInt(limit); - } - }, - }); -}); diff --git a/web_m2x_options/static/src/js/ir_options.js b/web_m2x_options/static/src/js/ir_options.js deleted file mode 100644 index 293c66c51..000000000 --- a/web_m2x_options/static/src/js/ir_options.js +++ /dev/null @@ -1,12 +0,0 @@ -/* Copyright 2020 Tecnativa - Carlos Roca - * * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ -odoo.define("web_m2x_options.ir_options", function (require) { - "use strict"; - - var rpc = require("web.rpc"); - - return rpc.query({ - model: "ir.config_parameter", - method: "get_web_m2x_options", - }); -}); diff --git a/web_m2x_options/static/src/xml/base.xml b/web_m2x_options/static/src/xml/base.xml deleted file mode 100644 index d2ac41b8a..000000000 --- a/web_m2x_options/static/src/xml/base.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - !(widget.nodeOptions.no_open || widget.nodeOptions.no_open_edit || widget.noOpen) - - - -