diff options
| author | Géry Debongnie <ged@odoo.com> | 2017-02-27 19:39:41 +0100 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-04-11 19:44:38 +0200 |
| commit | 206453c83689ad0dc6ceec3686153ab79bf88660 (patch) | |
| tree | 4cffbfbcb5ca9467abf35f595caa24fbe7761a6b /addons/web/static/src | |
| parent | f79399bf7720e7a69b95fe2c548208b56b94ce29 (diff) | |
[FIX] web: various fixes with one2many/formviewdialogs
We share the basic model record between views, and we have to make sure
that everything is properly coordinated. The main issue solved by this
commit is the fact that we can now edit a one2many record, then close
the dialog, then edit it again and finally cancel the changes, which
bring us to the previous step.
Also, we had to slightly improve the AbstractView API
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/js/chrome/view_manager.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/relational_fields.js | 17 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/abstract_view.js | 35 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 27 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_view.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form/form_controller.js | 11 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_column.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/view_dialogs.js | 34 |
8 files changed, 112 insertions, 33 deletions
diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index 0566b3042be..d2700854fe3 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -349,7 +349,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { } view = new View(arch, view.fields_view.fields, params); - return view.createController(this).then(function(controller) { + return view.getController(this).then(function(controller) { controller.on('history_back', this, function() { if (self.action_manager) self.action_manager.trigger('history_back'); }); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 1915fa6c9ff..d8abf196722 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -853,20 +853,13 @@ var FieldOne2Many = FieldX2Many.extend({ */ _onOpenRecord: function (event) { var self = this; - this._super.apply(this, arguments); _.extend(event.data, { - on_save: function (record) { - // update in a form view opened from the o2m (kanban or non editable list) - var relRecord = _.find(self.value.data, function (d) { - return d.res_id === record.res_id; - }); - self._setValue({ - operation: 'UPDATE', - id: relRecord.id, - data: record.data, - }); + shouldSaveLocally: true, + on_saved: function () { + self._setValue({ operation: 'NOOP' }); }, }); + this._super.apply(this, arguments); }, }); @@ -926,10 +919,10 @@ var FieldMany2Many = FieldX2Many.extend({ * @param {OdooEvent} */ _onOpenRecord: function (event) { - this._super.apply(this, arguments); _.extend(event.data, { on_saved: this.trigger_up.bind(this, 'reload', {db_id: event.data.id}), }); + this._super.apply(this, arguments); }, }); diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 22514f3ba75..98c6aaac22e 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -14,7 +14,7 @@ odoo.define('web.AbstractView', function (require) { * * JS views are supposed to be used like this: * 1. instantiate a view with some arch, fields and params - * 2. call the createController method on the view instance. This returns a + * 2. call the getController method on the view instance. This returns a * controller (with a model and a renderer as sub widgets) * 3. append the controller somewhere * @@ -113,12 +113,12 @@ var AbstractView = Class.extend({ * likely a view manager) * @returns {Deferred} The deferred resolves to a controller */ - createController: function (parent) { + getController: function (parent) { var self = this; - var model = this.model || this.createModel(parent); - return $.when(model.load(this.loadParams), this._loadLibs()).then(function () { + return $.when(this._loadData(parent), this._loadLibs()).then(function () { + var model = self.getModel(); var state = model.get(arguments[0]); - var renderer = self.createRenderer(parent, state); + var renderer = self.getRenderer(parent, state); var Controller = self.Controller || self.config.Controller; var controllerParams = _.extend({ initialState: state, @@ -134,16 +134,19 @@ var AbstractView = Class.extend({ return controller; }); }, - createModel: function (parent) { - var Model = this.config.Model; - return new Model(parent); + getModel: function (parent) { + if (!this.model) { + var Model = this.config.Model; + this.model = new Model(parent); + } + return this.model; }, - createRenderer: function (parent, state) { + getRenderer: function (parent, state) { var Renderer = this.config.Renderer; return new Renderer(parent, state, this.rendererParams); }, /** - * this is usedful to customize the actual class to use before calling + * this is useful to customize the actual class to use before calling * createView. * * @param {Controller} Controller @@ -157,6 +160,18 @@ var AbstractView = Class.extend({ //-------------------------------------------------------------------------- /** + * Load initial data from the model + * + * @private + * @param {Widget} parent the parent of the model, if it has to be created + * @returns {Deferred<*>} a deferred that resolves to whatever the model + * decide to return + */ + _loadData: function (parent) { + var model = this.getModel(parent); + return model.load(this.loadParams); + }, + /** * Makes sure that the js_libs and css_libs are properly loaded. Note that * the ajax loadJS and loadCSS methods don't do anything if the given file * is already loaded. diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 785cf79775b..bc36f454914 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -403,6 +403,7 @@ var BasicModel = AbstractModel.extend({ * of all fields by default * @param {Object} params.fields contains the description of each field * @param {string} [params.type] 'record' or 'list' + * @param {string} [params.recordID] an ID for an existing resource. * @returns {Deferred -> string} resolves to a local id, or handle */ load: function (params) { @@ -733,11 +734,22 @@ var BasicModel = AbstractModel.extend({ * @param {string} record_id local resource * @param {Object} [options] * @param {boolean} [options.reload=true] if true, data will be reloaded + * @param {boolean} [options.localSave=false] if true, the record will only + * be 'locally' saved: its changes will move from the _changes key to the + * data key * @returns {Deferred} */ save: function (record_id, options) { var self = this; options = options || {}; + if (options.localSave) { + var record = self.localData[record_id]; + if (record._changes) { + _.extend(record.data, record._changes); + record._changes = null; + } + return $.when(); + } var shouldReload = 'reload' in options ? options.reload : true; return this.mutex.exec(function () { var record = self.localData[record_id]; @@ -800,6 +812,21 @@ var BasicModel = AbstractModel.extend({ }); }, /** + * Set and return the fieldattrs on a record element. + * It is mostly useful for the cases where a record element is shared + * between various views, such as a one2many with a tree and a form view. + * + * @param {string} recordID a valid element ID + * @param {Object} attrs the new field attrs + * @return {Object} the previous field attrs + */ + setFieldAttrs: function (recordID, attrs) { + var record = this.localData[recordID]; + var result = record.fieldAttrs; + record.fieldAttrs = attrs; + return result; + }, + /** * For list resources, this changes the orderedBy key, then performs the * sort directly, in javascript. This is used for sorting static datasets, * such as a one2many in a form view. For dynamic datasets, such as a list diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 947a3ad26b6..aea0b88dcf3 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -99,6 +99,7 @@ var BasicView = AbstractView.extend({ this.loadParams.fieldNames = fieldNames; this.loadParams.fields = fields; this.loadParams.limit = parseInt(arch.attrs.limit, 10) || params.limit; + this.recordID = params.recordID; }, //-------------------------------------------------------------------------- @@ -106,6 +107,19 @@ var BasicView = AbstractView.extend({ //-------------------------------------------------------------------------- /** + * In some cases, we already have a preloaded record + * + * @override + * @private + * @returns {Deferred} + */ + _loadData: function () { + if (this.recordID) { + return $.when(this.recordID); + } + return this._super.apply(this, arguments); + }, + /** * Visit all nodes in the arch field and process each fields * * @param {Object} arch diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 1493297ae97..df27ef0135b 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -183,6 +183,9 @@ var FormController = BasicController.extend({ * readonly mode after saving the record * @param {boolean} [options.reload=true] if true, reload the record after * saving + * @param {boolean} [options.localSave=false] if true, the record will only + * be 'locally' saved: its changes will move from the _changes key to the + * data key * @returns {Deferred} */ saveRecord: function (options) { @@ -199,12 +202,11 @@ var FormController = BasicController.extend({ if (this.checkInvalidFields()) { return $.Deferred().reject(); } else { - return this.model.save(this.handle, {reload: shouldReload}) - .then(function (element) { + return this.model.save(this.handle, {reload: shouldReload, localSave: options.localSave}) + .then(function () { if (!stayInEdit) { self._toReadOnlyMode(); } - self.trigger_up('saved', _.clone(element.data)); }); } } @@ -411,6 +413,7 @@ var FormController = BasicController.extend({ var field = event.data.field; new dialogs.FormViewDialog(this, { res_model: field.relation, + shouldSaveLocally: true, domain: event.data.domain, context: event.data.context, on_save: event.data.on_save, @@ -539,6 +542,8 @@ var FormController = BasicController.extend({ on_saved: event.data.on_saved, model: self.model, parentID: self.handle, + recordID: record.id, + shouldSaveLocally: event.data.shouldSaveLocally, }).open(); }); }, diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index 5875ac21e31..b93f75cd9df 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -220,13 +220,12 @@ var KanbanColumn = Widget.extend({ edit_column: function (event) { event.preventDefault(); - var dialog = new view_dialogs.FormViewDialog(this, { + new view_dialogs.FormViewDialog(this, { res_model: this.relation, res_id: this.id, title: _t("Edit Column"), + on_saved: this.trigger_up.bind(this, 'reload'), }).open(); - - dialog.on('saved', null, this.trigger_up.bind(this, 'reload')); }, delete_record: function (event) { diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index f5982e2ef65..ab9588dc740 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -82,6 +82,10 @@ var FormViewDialog = ViewDialog.extend({ * @param {function} [options.on_saved] callback executed after on_save * @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; @@ -91,6 +95,8 @@ var FormViewDialog = ViewDialog.extend({ this.context = options.context; this.model = options.model; this.parentID = options.parentID; + this.recordID = options.recordID; + this.shouldSaveLocally = options.shouldSaveLocally; var multi_select = !_.isNumber(options.res_id) && !options.disable_multiple_selection; var readonly = _.isNumber(options.res_id) && options.readonly; @@ -130,7 +136,15 @@ var FormViewDialog = ViewDialog.extend({ } this._super(parent, options); }, - + /** + * If we changed some fieldAttrs, we need to restore them now. + */ + destroy: function () { + if (this.recordID && this.fieldAttrs) { + this.model.setFieldAttrs(this.recordID, this.fieldAttrs); + } + this._super.apply(this, arguments); + }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- @@ -153,6 +167,10 @@ var FormViewDialog = ViewDialog.extend({ } fields_view_def.then(function (fields_view) { + if (self.recordID) { + self.fieldAttrs = + self.model.setFieldAttrs(self.recordID, fields_view.fieldAttrs); + } var formview = new FormView(fields_view.arch, fields_view.fields, { modelName: self.res_model, context: self.context, @@ -164,11 +182,15 @@ var FormViewDialog = ViewDialog.extend({ default_buttons: false, model: self.model, parentID: self.parentID, + recordID: self.recordID, }); - return formview.createController(self); + 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, {localSave: true}); + } self.form_view.appendTo(fragment) .then(function () { var $buttons = $('<div>'); @@ -197,7 +219,11 @@ var FormViewDialog = ViewDialog.extend({ } def = this.options.on_save(this.form_view.model.get(this.form_view.handle)); } else { - def = this.form_view.saveRecord({stayInEdit: true, reload: false}); + def = this.form_view.saveRecord({ + stayInEdit: true, + reload: false, + localSave: this.shouldSaveLocally, + }); } return $.when(def).then(function () { // record might have been changed by the save (e.g. if this was a new record, it has an @@ -316,7 +342,7 @@ var SelectCreateDialog = ViewDialog.extend({ hasSelectors: !self.options.disable_multiple_selection, }, self.options.list_view_options)); listView.setController(SelectCreateListController); - return listView.createController(self); + return listView.getController(self); }).then(function (controller) { self.list_controller = controller; // Set the dialog's buttons |
