Files
odoo_source/addons/web/static/src/js/views/view_dialogs.js
T

439 lines
16 KiB
JavaScript

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 ListController = require('web.ListController');
var ListView = require('web.ListView');
var view_registry = require('web.view_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)
*/
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.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: (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: (multi_select ? _t("Save & Close") : _t("Save")),
classes: "btn-primary",
click: function () {
this._save().then(self.close.bind(self));
}
});
if (multi_select) {
options.buttons.splice(1, 0, {
text: _t("Save & New"),
classes: "btn-primary",
click: function () {
this._save()
.then(self.form_view.createRecord.bind(self.form_view, self.parentID))
.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) {
options.buttons.push({
text: _t("Remove"),
classes: 'btn-secondary ' + oBtnRemove,
click: this._remove.bind(this),
});
}
}
}
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 = $.when(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,
});
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});
}
self.form_view.appendTo(fragment)
.then(function () {
self.opened().always(function () {
var $buttons = $('<div>');
self.form_view.renderButtons($buttons);
if ($buttons.children().length) {
self.$footer.empty().append($buttons.contents());
}
dom.append(self.$el, fragment, {
callbacks: [{widget: self.form_view}],
in_DOM: true,
});
});
_super();
});
});
return this;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_focusOnClose: function() {
this.trigger_up('form_dialog_discarded');
return true;
},
/**
* @private
*/
_remove: function () {
this.on_remove();
this.close();
},
/**
* @private
* @returns {Deferred}
*/
_save: function () {
var self = this;
return this.form_view.saveRecord(this.form_view.handle, {
stayInEdit: true,
reload: false,
savePoint: this.shouldSaveLocally,
viewType: 'form',
}).then(function (changedFields) {
// record might have been changed by the save (e.g. if this was a new record, it has an
// id now), so don't re-use the copy obtained before the save
var record = self.form_view.model.get(self.form_view.handle);
self.on_saved(record, !!changedFields.length);
});
},
});
var SelectCreateListController = ListController.extend({
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Override to select the clicked record instead of opening it
*
* @override
* @private
*/
_onOpenRecord: function (ev) {
var selectedRecord = this.model.get(ev.data.id);
this.trigger_up('select_record', {
id: selectedRecord.res_id,
display_name: selectedRecord.data.display_name,
});
},
});
/**
* Search dialog (displays a list of records and permits to create a new one by switching to a form view)
*/
var SelectCreateDialog = ViewDialog.extend({
custom_events: _.extend({}, ViewDialog.prototype.custom_events, {
select_record: function (event) {
if (!this.options.readonly) {
this.on_selected([event.data]);
this.close();
}
},
selection_changed: function (event) {
event.stopPropagation();
this.$footer.find(".o_select_button").prop('disabled', !event.data.selection.length);
},
}),
/**
* options:
* - initial_ids
* - initial_view: form or search (default search)
* - list_view_options: dict of options to pass to the List View
* - on_selected: optional callback to execute when records are selected
* - disable_multiple_selection: true to allow create/select multiple records
* - dynamicFilters: filters to add to the searchview
*/
init: function () {
this._super.apply(this, arguments);
_.defaults(this.options, { initial_view: 'search' });
this.on_selected = this.options.on_selected || (function () {});
this.initialIDs = this.options.initial_ids;
},
open: function () {
if (this.options.initial_view !== "search") {
return this.create_edit_record();
}
var self = this;
var _super = this._super.bind(this);
this.loadViews(this.res_model, this.context, [[false, 'list'], [false, 'search']], {})
.then(this.setup.bind(this))
.then(function (fragment) {
self.opened().then(function () {
dom.append(self.$el, fragment, {
callbacks: [{widget: self.listController}],
in_DOM: true,
});
self.set_buttons(self.__buttons);
});
_super();
});
return this;
},
setup: function (fieldsViews) {
var self = this;
var fragment = document.createDocumentFragment();
var domain = this.domain;
if (this.initialIDs) {
domain = domain.concat([['id', 'in', this.initialIDs]]);
}
var listView = new ListView(fieldsViews.list, _.extend({
action: {
controlPanelFieldsView: fieldsViews.search,
},
action_buttons: false,
dynamicFilters: this.options.dynamicFilters,
context: this.context,
domain: domain,
hasSelectors: !this.options.disable_multiple_selection,
modelName: this.res_model,
readonly: true,
withBreadcrumbs: false,
}, this.options.list_view_options));
listView.setController(SelectCreateListController);
return listView.getController(this).then(function (controller) {
self.listController = controller;
// render the footer buttons
self.__buttons = [{
text: _t("Cancel"),
classes: 'btn-secondary o_form_button_cancel',
close: true,
}];
if (!self.options.no_create) {
self.__buttons.unshift({
text: _t("Create"),
classes: 'btn-primary',
click: self.create_edit_record.bind(self)
});
}
if (!self.options.disable_multiple_selection) {
self.__buttons.unshift({
text: _t("Select"),
classes: 'btn-primary o_select_button',
disabled: true,
close: true,
click: function () {
var records = self.listController.getSelectedRecords();
var values = _.map(records, function (record) {
return {
id: record.res_id,
display_name: record.data.display_name,
};
});
self.on_selected(values);
},
});
}
return self.listController.appendTo(fragment);
}).then(function () {
return fragment;
});
},
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() {
this.trigger_up('form_dialog_discarded');
return true;
},
});
return {
FormViewDialog: FormViewDialog,
SelectCreateDialog: SelectCreateDialog,
};
});