odoo.define('web.view_dialogs', function (require) { "use strict"; var config = require('web.config'); var core = require('web.core'); var Dialog = require('web.Dialog'); var dom = require('web.dom'); var view_registry = require('web.view_registry'); var select_create_controllers_registry = require('web.select_create_controllers_registry'); var _t = core._t; /** * Class with everything which is common between FormViewDialog and * SelectCreateDialog. */ var ViewDialog = Dialog.extend({ custom_events: _.extend({}, Dialog.prototype.custom_events, { push_state: '_onPushState', }), /** * @constructor * @param {Widget} parent * @param {options} [options] * @param {string} [options.dialogClass=o_act_window] * @param {string} [options.res_model] the model of the record(s) to open * @param {any[]} [options.domain] * @param {Object} [options.context] */ init: function (parent, options) { options = options || {}; options.fullscreen = config.device.isMobile; options.dialogClass = options.dialogClass || '' + ' o_act_window'; this._super(parent, $.extend(true, {}, options)); this.res_model = options.res_model || null; this.domain = options.domain || []; this.context = options.context || {}; this.options = _.extend(this.options || {}, options || {}); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * We stop all push_state events from bubbling up. It would be weird to * change the url because a dialog opened. * * @param {OdooEvent} event */ _onPushState: function (event) { event.stopPropagation(); }, }); /** * Create and edit dialog (displays a form view record and leave once saved) */ var FormViewDialog = ViewDialog.extend({ /** * @param {Widget} parent * @param {Object} [options] * @param {string} [options.parentID] the id of the parent record. It is * useful for situations such as a one2many opened in a form view dialog. * In that case, we want to be able to properly evaluate domains with the * 'parent' key. * @param {integer} [options.res_id] the id of the record to open * @param {Object} [options.form_view_options] dict of options to pass to * the Form View @todo: make it work * @param {Object} [options.fields_view] optional form fields_view * @param {boolean} [options.readonly=false] only applicable when not in * creation mode * @param {boolean} [options.deletable=false] whether or not the record can * be deleted * @param {boolean} [options.disable_multiple_selection=false] set to true * to remove the possibility to create several records in a row * @param {function} [options.on_saved] callback executed after saving a * record. It will be called with the record data, and a boolean which * indicates if something was changed * @param {function} [options.on_remove] callback executed when the user * clicks on the 'Remove' button * @param {BasicModel} [options.model] if given, it will be used instead of * a new form view model * @param {string} [options.recordID] if given, the model has to be given as * well, and in that case, it will be used without loading anything. * @param {boolean} [options.shouldSaveLocally] if true, the view dialog * will save locally instead of actually saving (useful for one2manys) * @param {function} [options._createContext] function to get context for name field * useful for many2many_tags widget where we want to removed default_name field * context. */ init: function (parent, options) { var self = this; options = options || {}; this.res_id = options.res_id || null; this.on_saved = options.on_saved || (function () {}); this.on_remove = options.on_remove || (function () {}); this.context = options.context; this._createContext = options._createContext; this.model = options.model; this.parentID = options.parentID; this.recordID = options.recordID; this.shouldSaveLocally = options.shouldSaveLocally; this.readonly = options.readonly; this.deletable = options.deletable; this.disable_multiple_selection = options.disable_multiple_selection; var oBtnRemove = 'o_btn_remove'; var multi_select = !_.isNumber(options.res_id) && !options.disable_multiple_selection; var readonly = _.isNumber(options.res_id) && options.readonly; if (!options.buttons) { options.buttons = [{ text: options.close_text || (readonly ? _t("Close") : _t("Discard")), classes: "btn-secondary o_form_button_cancel", close: true, click: function () { if (!readonly) { self.form_view.model.discardChanges(self.form_view.handle, { rollback: self.shouldSaveLocally, }); } }, }]; if (!readonly) { options.buttons.unshift({ text: options.save_text || (multi_select ? _t("Save & Close") : _t("Save")), classes: "btn-primary", click: function () { self._save().then(self.close.bind(self)); } }); if (multi_select) { options.buttons.splice(1, 0, { text: _t("Save & New"), classes: "btn-primary", click: function () { self._save() .then(function () { // reset default name field from context when Save & New is clicked, pass additional // context so that when getContext is called additional context resets it var additionalContext = self._createContext && self._createContext(false) || {}; self.form_view.createRecord(self.parentID, additionalContext); }) .then(function () { if (!self.deletable) { return; } self.deletable = false; self.buttons = self.buttons.filter(function (button) { return button.classes.split(' ').indexOf(oBtnRemove) < 0; }); self.set_buttons(self.buttons); self.set_title(_t("Create ") + _.str.strRight(self.title, _t("Open: "))); }); }, }); } var multi = options.disable_multiple_selection; if (!multi && this.deletable) { this._setRemoveButtonOption(options, oBtnRemove); } } } this._super(parent, options); }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- /** * Open the form view dialog. It is necessarily asynchronous, but this * method returns immediately. * * @returns {FormViewDialog} this instance */ open: function () { var self = this; var _super = this._super.bind(this); var FormView = view_registry.get('form'); var fields_view_def; if (this.options.fields_view) { fields_view_def = Promise.resolve(this.options.fields_view); } else { fields_view_def = this.loadFieldView(this.res_model, this.context, this.options.view_id, 'form'); } fields_view_def.then(function (viewInfo) { var refinedContext = _.pick(self.context, function (value, key) { return key.indexOf('_view_ref') === -1; }); var formview = new FormView(viewInfo, { modelName: self.res_model, context: refinedContext, ids: self.res_id ? [self.res_id] : [], currentId: self.res_id || undefined, index: 0, mode: self.res_id && self.options.readonly ? 'readonly' : 'edit', footerToButtons: true, default_buttons: false, withControlPanel: false, model: self.model, parentID: self.parentID, recordID: self.recordID, isFromFormViewDialog: true, }); return formview.getController(self); }).then(function (formView) { self.form_view = formView; var fragment = document.createDocumentFragment(); if (self.recordID && self.shouldSaveLocally) { self.model.save(self.recordID, {savePoint: true}); } return self.form_view.appendTo(fragment) .then(function () { self.opened().then(function () { var $buttons = $('
%s
", _t("No records found!")), }, action_buttons: false, dynamicFilters: this.options.dynamicFilters, context: this.context, domain: domain, modelName: this.res_model, withBreadcrumbs: false, withSearchPanel: false, })); view.setController(selectCreateController); return view.getController(this).then(function (controller) { self.viewController = controller; // render the footer buttons self._prepareButtons(); return self.viewController.appendTo(fragment); }).then(function () { return fragment; }); }, close: function () { this._super.apply(this, arguments); this.on_closed(); }, create_edit_record: function () { var self = this; var dialog = new FormViewDialog(this, _.extend({}, this.options, { on_saved: function (record) { var values = [{ id: record.res_id, display_name: record.data.display_name || record.data.name, }]; self.on_selected(values); }, })).open(); dialog.on('closed', this, this.close); return dialog; }, /** * @override */ _focusOnClose: function() { var isFocusSet = false; this.trigger_up('form_dialog_discarded', { callback: function (isFocused) { isFocusSet = isFocused; }, }); return isFocusSet; }, /** * prepare buttons for dialog footer based on options * * @private */ _prepareButtons: function () { this.__buttons = [{ text: _t("Cancel"), classes: 'btn-secondary o_form_button_cancel', close: true, }]; if (!this.options.no_create) { this.__buttons.unshift({ text: _t("Create"), classes: 'btn-primary', click: this.create_edit_record.bind(this) }); } if (!this.options.disable_multiple_selection) { this.__buttons.unshift({ text: _t("Select"), classes: 'btn-primary o_select_button', disabled: true, close: true, click: function () { var records = this.viewController.getSelectedRecords(); var values = _.map(records, function (record) { return { id: record.res_id, display_name: record.data.display_name, }; }); this.on_selected(values); }, }); } }, }); return { FormViewDialog: FormViewDialog, SelectCreateDialog: SelectCreateDialog, }; });