summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views
diff options
context:
space:
mode:
authorVincent Schippefilt <vsc@odoo.com>2019-03-06 14:02:32 +0100
committerAaron Bohy <aab@odoo.com>2019-03-06 20:07:17 +0100
commitab56e637b7f97ddcdbcc9baea99ae097e342409c (patch)
tree4cb47da9f7ef89b19d18429220a7507470ec12a9 /addons/web/static/src/js/views
parenteeed894fd50e477135e1ac9a82f6e98ce063ab90 (diff)
[REF] web: adapt code after jQuery update
Part of task 1896658 Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Christophe Matthieu <chm@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com> Co-authored-by: David Monjoie <dmo@odoo.com> Co-authored-by: Martin Geubelle <mge@odoo.com> Co-authored-by: svs-odoo <svs@odoo.com> Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
Diffstat (limited to 'addons/web/static/src/js/views')
-rw-r--r--addons/web/static/src/js/views/abstract_controller.js75
-rw-r--r--addons/web/static/src/js/views/abstract_model.js2
-rw-r--r--addons/web/static/src/js/views/abstract_renderer.js14
-rw-r--r--addons/web/static/src/js/views/abstract_view.js7
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js112
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js417
-rw-r--r--addons/web/static/src/js/views/basic/basic_renderer.js63
-rw-r--r--addons/web/static/src/js/views/basic/basic_view.js4
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js12
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js49
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_renderer.js46
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_controller.js6
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_model.js14
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_renderer.js14
-rw-r--r--addons/web/static/src/js/views/control_panel/search/filter_menu.js4
-rw-r--r--addons/web/static/src/js/views/control_panel/search/search_bar.js19
-rw-r--r--addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js24
-rw-r--r--addons/web/static/src/js/views/control_panel/search/search_filters.js8
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js16
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js51
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js119
-rw-r--r--addons/web/static/src/js/views/form/form_view.js4
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js10
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js2
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_column.js13
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js39
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_model.js33
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_record.js49
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_record_quick_create.js21
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js62
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js53
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_view.js2
-rw-r--r--addons/web/static/src/js/views/kanban/quick_create_form_view.js2
-rw-r--r--addons/web/static/src/js/views/kanban/search_panel.js30
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js49
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js122
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js92
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_model.js23
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_renderer.js2
-rw-r--r--addons/web/static/src/js/views/standalone_field_manager_mixin.js2
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js19
-rw-r--r--addons/web/static/src/js/views/view_utils.js2
42 files changed, 914 insertions, 793 deletions
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js
index b5d7c8fe8b3..0c251874480 100644
--- a/addons/web/static/src/js/views/abstract_controller.js
+++ b/addons/web/static/src/js/views/abstract_controller.js
@@ -62,7 +62,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
/**
* Simply renders and updates the url.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
var self = this;
@@ -70,12 +70,16 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
this.$el.addClass('o_view_controller');
return this._super.apply(this, arguments).then(function () {
+ var prom;
if (self._controlPanel) {
// render the ControlPanel elements (buttons, pager, sidebar...)
- self.controlPanelElements = self._renderControlPanelElements();
- self._controlPanel.$el.prependTo(self.$el);
+ prom = self._renderControlPanelElements().then(function (elements) {
+ self.controlPanelElements = elements;
+ self._controlPanel.$el.prependTo(self.$el);
+ });
}
-
+ return Promise.resolve(prom);
+ }).then(function () {
return self._update(self.initialState);
});
},
@@ -119,7 +123,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*/
canBeRemoved: function () {
// AAB: get rid of this option when on_hashchange mechanism is improved
- return this.discardChanges(undefined, {readonlyIfRealDiscard: true});
+ return this.discardChanges(undefined, { readonlyIfRealDiscard: true });
},
/**
* Discards the changes made on the record associated to the given ID, or
@@ -128,15 +132,15 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* this method on the active view to make sure it is ok to open the home
* screen (and lose all current state).
*
- * Note that it returns a deferred, because the view could choose to ask the
+ * Note that it returns a Promise, because the view could choose to ask the
* user if he agrees to discard.
*
* @param {string} [recordID]
* if not given, we consider all the changes made by the controller
- * @returns {Deferred} resolved if properly discarded, rejected otherwise
+ * @returns {Promise} resolved if properly discarded, rejected otherwise
*/
discardChanges: function (recordID) {
- return $.when();
+ return Promise.resolve();
},
/**
* Export the state of the controller containing information that is shared
@@ -155,7 +159,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
/**
* Gives the focus to the renderer
*/
- giveFocus: function() {
+ giveFocus: function () {
this.renderer.giveFocus();
},
/**
@@ -174,7 +178,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* Short helper method to reload the view
*
* @param {Object} [params] This object will simply be given to the update
- * @returns {Deferred}
+ * @returns {Promise}
*/
reload: function (params) {
params = params || {};
@@ -186,7 +190,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
params = _.extend({}, params, searchQuery);
});
}
- return $.when(def).then(this.update.bind(this, params, {}));
+ return Promise.resolve(def).then(this.update.bind(this, params, {}));
},
/**
* For views that require a pager, this method will be called to allow the
@@ -195,16 +199,20 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* your view does not want a pager, just let this method empty.
*
* @param {jQuery Node} $node
+ * @return {Promise}
*/
renderPager: function ($node) {
+ return Promise.resolve();
},
/**
* Same as renderPager, but for the 'sidebar' zone (the zone with the menu
* dropdown in the control panel next to the buttons)
*
* @param {jQuery Node} $node
+ * @return {Promise}
*/
renderSidebar: function ($node) {
+ return Promise.resolve();
},
/**
* This is the main entry point for the controller. Changes from the search
@@ -219,12 +227,12 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* @param {Object} [options]
* @param {boolean} [options.reload=true] if true, the model will reload data
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
update: function (params, options) {
var self = this;
var shouldReload = (options && 'reload' in options) ? options.reload : true;
- var def = shouldReload ? this.model.reload(this.handle, params) : $.when();
+ var def = shouldReload ? this.model.reload(this.handle, params) : Promise.resolve();
// we check here that the updateIndex of the control panel hasn't changed
// between the start of the update request and the moment the controller
// asks the control panel to update itself ; indeed, it could happen that
@@ -285,17 +293,17 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* return {'html': '<h1>hello, world</h1>'}
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderBanner: function () {
if (this.bannerRoute !== undefined) {
var self = this;
return this.dp
- .add(this._rpc({route: this.bannerRoute}))
+ .add(this._rpc({ route: this.bannerRoute }))
.then(function (response) {
if (!response.html) {
self.$el.removeClass('o_has_banner');
- return $.when();
+ return Promise.resolve();
}
self.$el.addClass('o_has_banner');
var $banner = $(response.html);
@@ -313,22 +321,24 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
defs.push(ajax.loadJS(js.src));
js.remove();
});
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
$banner.prependTo(self.$('> .o_content'));
self._$banner = $banner;
});
});
}
- return $.when();
+ return Promise.resolve();
},
/**
* Renders the control elements (buttons, pager and sidebar) of the current
* view.
*
* @private
- * @returns {Object} an object containing the control panel jQuery elements
+ * @returns {Promise<Object>} resolved with an object containing the control
+ * panel jQuery elements
*/
_renderControlPanelElements: function () {
+ var self = this;
var elements = {
$buttons: $('<div>'),
$sidebar: $('<div>'),
@@ -336,15 +346,18 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
};
this.renderButtons(elements.$buttons);
- this.renderSidebar(elements.$sidebar);
- this.renderPager(elements.$pager);
- // remove the unnecessary outer div
- elements = _.mapObject(elements, function($node) {
- return $node && $node.contents();
- });
- elements.$switch_buttons = this._renderSwitchButtons();
+ var sidebarProm = this.renderSidebar(elements.$sidebar);
+ var pagerProm = this.renderPager(elements.$pager);
- return elements;
+ return Promise.all([sidebarProm, pagerProm]).then(function () {
+ // remove the unnecessary outer div
+ elements = _.mapObject(elements, function ($node) {
+ return $node && $node.contents();
+ });
+ elements.$switch_buttons = self._renderSwitchButtons();
+
+ return elements;
+ });
},
/**
* Renders the switch buttons and binds listeners on them.
@@ -354,7 +367,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*/
_renderSwitchButtons: function () {
var self = this;
- var views = _.filter(this.actionViews, {multiRecord: this.isMultiRecord});
+ var views = _.filter(this.actionViews, { multiRecord: this.isMultiRecord });
if (views.length <= 1) {
return $();
@@ -373,7 +386,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
var $switchButtonsFiltered = config.device.isMobile ? $switchButtons.find('button') : $switchButtons.filter('button');
$switchButtonsFiltered.click(_.debounce(function (event) {
var viewType = $(event.target).data('view-type');
- self.trigger_up('switch_view', {view_type: viewType});
+ self.trigger_up('switch_view', { view_type: viewType });
}, 200, true));
// set active view's icon as view switcher button's icon in mobile
@@ -402,7 +415,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*
* @private
* @param {Object} state the state given by the model
- * @returns {Deferred}
+ * @returns {Promise}
*/
_update: function () {
// AAB: update the control panel -> this will be moved elsewhere at some point
@@ -524,7 +537,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*/
_onOpenRecord: function (ev) {
ev.stopPropagation();
- var record = this.model.get(ev.data.id, {raw: true});
+ var record = this.model.get(ev.data.id, { raw: true });
this.trigger_up('switch_view', {
view_type: 'form',
res_id: record.res_id,
diff --git a/addons/web/static/src/js/views/abstract_model.js b/addons/web/static/src/js/views/abstract_model.js
index da73ac664a3..d18367b1756 100644
--- a/addons/web/static/src/js/views/abstract_model.js
+++ b/addons/web/static/src/js/views/abstract_model.js
@@ -31,7 +31,7 @@ var AbstractModel = mvc.Model.extend({
* @returns {Promise}
*/
reload: function (params) {
- return $.when();
+ return Promise.resolve();
},
/**
* Processes date(time) and selection field values sent by the server.
diff --git a/addons/web/static/src/js/views/abstract_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js
index 0f18263c9ee..724b63bae79 100644
--- a/addons/web/static/src/js/views/abstract_renderer.js
+++ b/addons/web/static/src/js/views/abstract_renderer.js
@@ -23,14 +23,14 @@ return mvc.Renderer.extend({
this.noContentHelp = params.noContentHelp;
},
/**
- * The rendering can be asynchronous (but it is not encouraged). The start
+ * The rendering is asynchronous. The start
* method simply makes sure that we render the view.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this.$el.addClass(this.arch.attrs.class);
- return $.when(this._render(), this._super());
+ return Promise.all([this._render(), this._super()]);
},
/**
* Called each time the renderer is attached into the DOM.
@@ -86,11 +86,11 @@ return mvc.Renderer.extend({
* @param {Object} params
* @param {boolean} [params.noRender=false]
* if true, the method only updates the state without rerendering
- * @returns {Deferred}
+ * @returns {Promise}
*/
updateState: function (state, params) {
this.state = state;
- return params.noRender ? $.when() : this._render();
+ return params.noRender ? Promise.resolve() : this._render();
},
//--------------------------------------------------------------------------
@@ -102,10 +102,10 @@ return mvc.Renderer.extend({
*
* @abstract
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
- return $.when();
+ return Promise.resolve();
},
});
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index ce8951f6ee8..ba3c198bd21 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -194,11 +194,12 @@ var AbstractView = Factory.extend({
def = this._createControlPanel(parent);
}
var _super = this._super.bind(this);
- return $.when(def).then(function (controlPanel) {
+ return Promise.resolve(def).then(function (controlPanel) {
// get the parent of the model if it already exists, as _super will
// set the new controller as parent, which we don't want
var modelParent = self.model && self.model.getParent();
- return _super(parent).done(function (controller) {
+ var prom = _super(parent);
+ prom.then(function (controller) {
if (controlPanel) {
controlPanel.setParent(controller);
}
@@ -207,7 +208,7 @@ var AbstractView = Factory.extend({
self.model.setParent(modelParent);
}
});
-
+ return prom;
});
},
/**
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index 07646c83273..edfaba60b54 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -41,11 +41,11 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
// savingDef is used to ensure that we always wait for pending save
// operations to complete before checking if there are changes to
// discard when discardChanges is called
- this.savingDef = $.when();
+ this.savingDef = Promise.resolve();
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
// add classname to reflect the (absence of) access rights (used to
@@ -66,24 +66,27 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
*
* @override
* @param {string} [recordID] - default to main recordID
- * @returns {Deferred<boolean>}
+ * @returns {Promise<boolean>}
* resolved if can be discarded, a boolean value is given to tells
* if there is something to discard or not
* rejected otherwise
*/
canBeDiscarded: function (recordID) {
+ var self = this;
if (!this.isDirty(recordID)) {
- return $.when(false);
+ return Promise.resolve(false);
}
var message = _t("The record has been modified, your changes will be discarded. Do you want to proceed?");
- var def = $.Deferred();
- var dialog = Dialog.confirm(this, message, {
- title: _t("Warning"),
- confirm_callback: def.resolve.bind(def, true),
- cancel_callback: def.reject.bind(def),
+ var def;
+ def = new Promise(function (resolve, reject) {
+ var dialog = Dialog.confirm(self, message, {
+ title: _t("Warning"),
+ confirm_callback: resolve.bind(self, true),
+ cancel_callback: reject,
+ });
+ dialog.on('closed', def, reject);
});
- dialog.on('closed', def, def.reject);
return def;
},
/**
@@ -114,7 +117,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* @see _.discardChanges
*/
discardChanges: function (recordID, options) {
- return $.when(this.mutex.getUnlockedDef(), this.savingDef)
+ return Promise.all([this.mutex.getUnlockedDef(), this.savingDef])
.then(this._discardChanges.bind(this, recordID || this.handle, options));
},
/**
@@ -144,6 +147,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* @override
*/
renderPager: function ($node, options) {
+ var self = this;
var data = this.model.get(this.handle, {raw: true});
this.pager = new Pager(this, data.count, data.offset + 1, data.limit, options);
@@ -161,19 +165,21 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
})
.then(this.pager.enable.bind(this.pager));
});
- this.pager.appendTo($node);
- this._updatePager(); // to force proper visibility
+ return this.pager.appendTo($node).then(function() {
+ self._updatePager(); // to force proper visibility
+ });
},
/**
* Saves the record whose ID is given if necessary (@see _saveRecord).
*
* @param {string} [recordID] - default to main recordID
* @param {Object} [options]
- * @returns {Deferred}
+ * @returns {Promise}
* Resolved with the list of field names (whose value has been modified)
* Rejected if the record can't be saved
*/
saveRecord: function (recordID, options) {
+ var self = this;
// Some field widgets can't detect (all) their changes immediately or
// may have to validate them before notifying them, so we ask them to
// commit their current value before saving. This has to be done outside
@@ -187,23 +193,22 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
// user if some discarding has to be made). This operation must also be
// mutex-protected as commitChanges function of x2m has to be aware of
// all final changes made to a row.
- var self = this;
- var savingDef = $.Deferred();
- this.savingDef = savingDef;
-
- return this.mutex.getUnlockedDef()
+ var unlockedMutex = this.mutex.getUnlockedDef()
.then(function () {
return self.renderer.commitChanges(recordID || self.handle);
})
.then(function () {
return self.mutex.exec(self._saveRecord.bind(self, recordID, options));
- })
- .then(savingDef.resolve.bind(savingDef))
- .fail(savingDef.resolve.bind(savingDef));
+ });
+ this.savingDef = new Promise(function (resolve) {
+ unlockedMutex.then(resolve).guardedCatch(resolve);
+ });
+
+ return unlockedMutex;
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
update: function (params, options) {
var self = this;
@@ -267,31 +272,32 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* @private
* @param {Object} attrs the attrs of the button clicked
* @param {Object} [record] the current state of the view
- * @returns {Deferred}
+ * @returns {Promise}
*/
_callButtonAction: function (attrs, record) {
var self = this;
- var def = $.Deferred();
- var reload = function () {
- return self.isDestroyed() ? $.when() : self.reload();
- };
- record = record || this.model.get(this.handle);
+ var def = new Promise(function (resolve, reject) {
+ var reload = function () {
+ return self.isDestroyed() ? Promise.resolve() : self.reload();
+ };
+ record = record || self.model.get(self.handle);
- this.trigger_up('execute_action', {
- action_data: _.extend({}, attrs, {
- context: record.getContext({additionalContext: attrs.context || {}}),
- }),
- env: {
- context: record.getContext(),
- currentID: record.data.id,
- model: record.model,
- resIDs: record.res_ids,
- },
- on_success: def.resolve.bind(def),
- on_fail: function () {
- self.update({}, {reload: false}).always(def.reject.bind(def));
- },
- on_closed: reload,
+ self.trigger_up('execute_action', {
+ action_data: _.extend({}, attrs, {
+ context: record.getContext({additionalContext: attrs.context || {}}),
+ }),
+ env: {
+ context: record.getContext(),
+ currentID: record.data.id,
+ model: record.model,
+ resIDs: record.res_ids,
+ },
+ on_success: resolve,
+ on_fail: function () {
+ self.update({}, {reload: false}).then(reject).guardedCatch(reject);
+ },
+ on_closed: reload,
+ });
});
return this.alive(def);
},
@@ -305,7 +311,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* @param {string} id - the id of one of the view's records
* @param {string[]} fields - the changed fields
* @param {OdooEvent} e - the event that triggered the change
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmChange: function (id, fields, e) {
if (e.name === 'discard_changes' && e.target.reset) {
@@ -364,7 +370,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* the new record to be in edit mode too, but the view manager calls
* this function as the URL changes...) @todo get rid of this when
* the webclient/action_manager's hashchange mechanism is improved.
- * @returns {Deferred}
+ * @returns {Promise}
*/
_discardChanges: function (recordID, options) {
var self = this;
@@ -459,7 +465,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* @param {boolean} [options.savePoint=false]
* if true, the record will only be 'locally' saved: its changes
* will move from the _changes key to the data key
- * @returns {Deferred}
+ * @returns {Promise}
* Resolved with the list of field names (whose value has been modified)
* Rejected if the record can't be saved
*/
@@ -490,7 +496,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
}
return saveDef;
} else {
- return $.Deferred().reject(); // Cannot be saved
+ return Promise.reject("SaveRecord: this.canBeSave is false"); // Cannot be saved
}
},
/**
@@ -501,7 +507,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* @private
* @param {string} mode - 'readonly' or 'edit'
* @param {string} [recordID]
- * @returns {Deferred}
+ * @returns {Promise}
*/
_setMode: function (mode, recordID) {
if ((recordID || this.handle) === this.handle) {
@@ -511,7 +517,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
core.bus.trigger('DOM_updated');
});
}
- return $.when();
+ return Promise.resolve();
},
/**
* Helper method, to get the current environment variables from the model
@@ -564,14 +570,14 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
ev.stopPropagation();
var recordID = ev.data.recordID;
this._discardChanges(recordID)
- .done(function () {
+ .then(function () {
// TODO this will tell the renderer to rerender the widget that
// asked for the discard but will unfortunately lose the click
// made on another row if any
self._confirmChange(recordID, [ev.data.fieldName], ev)
- .always(ev.data.onSuccess);
+ .then(ev.data.onSuccess).guardedCatch(ev.data.onSuccess);
})
- .fail(ev.data.onFailure);
+ .guardedCatch(ev.data.onFailure);
},
/**
* Forces to save directly the changes if the controller is in readonly,
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 4d86ef4293b..95b6d439ec1 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -173,7 +173,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {string} [options.position=top] if the new record should be added
* on top or on bottom of the list
- * @returns {Deferred<string>} resolves to the id of the new created record
+ * @returns {Promise<string>} resolves to the id of the new created record
*/
addDefaultRecord: function (listID, options) {
var self = this;
@@ -241,7 +241,7 @@ var BasicModel = AbstractModel.extend({
* assume main viewType from the record
* @param {Array} [options.fieldNames] list of field names for which a
* default value must be generated (used to complete the values dict)
- * @returns {Deferred}
+ * @returns {Promise}
*/
applyDefaultValues: function (recordID, values, options) {
options = options || {};
@@ -304,7 +304,7 @@ var BasicModel = AbstractModel.extend({
}
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Onchange RPCs may return values for fields that are not in the current
@@ -321,7 +321,7 @@ var BasicModel = AbstractModel.extend({
*
* @param {string} recordID local resource id of a record
* @param {string} viewType the current viewType
- * @returns {Deferred<string>} resolves to the id of the record
+ * @returns {Promise<string>} resolves to the id of the record
*/
applyRawChanges: function (recordID, viewType) {
var record = this.localData[recordID];
@@ -388,7 +388,7 @@ var BasicModel = AbstractModel.extend({
* @param {string[]} recordIds list of local resources ids. They should all
* be of type 'record', be of the same model and have the same parent.
* @param {string} modelName mode name used to unlink the records
- * @returns {Deferred}
+ * @returns {Promise}
*/
deleteRecords: function (recordIds, modelName) {
var self = this;
@@ -457,7 +457,7 @@ var BasicModel = AbstractModel.extend({
* Duplicate a record (by calling the 'copy' route)
*
* @param {string} recordID id for a local resource
- * @returns {Deferred<string>} resolves to the id of duplicate record
+ * @returns {Promise<string>} resolves to the id of duplicate record
*/
duplicateRecord: function (recordID) {
var self = this;
@@ -725,7 +725,7 @@ var BasicModel = AbstractModel.extend({
* @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
+ * @returns {Promise<string>} resolves to a local id, or handle
*/
load: function (params) {
params.type = params.type || (params.res_id !== undefined ? 'record' : 'list');
@@ -758,7 +758,7 @@ var BasicModel = AbstractModel.extend({
* @param {string} model name of the model
* @param {Object[]} fields a description of field properties
* @param {Object} [fieldInfo] various field info that we want to set
- * @returns {string} the local id for the created resource
+ * @returns {Promise<string>} the local id for the created resource
*/
makeRecord: function (model, fields, fieldInfo) {
var self = this;
@@ -849,7 +849,7 @@ var BasicModel = AbstractModel.extend({
record.data[field.name] = field.value;
}
});
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
return record.id;
});
},
@@ -863,7 +863,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} changes a map field => new value
* @param {Object} [options] will be transferred to the applyChange method
* @see _applyChange
- * @returns {string[]} list of changed fields
+ * @returns {Promise<string[]>} list of changed fields
*/
notifyChanges: function (record_id, changes, options) {
return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options));
@@ -876,7 +876,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {boolean} [options.keepChanges=false] if true, doesn't discard the
* changes on the record before reloading it
- * @returns {Deferred<string>} resolves to the id of the resource
+ * @returns {Promise<string>} resolves to the id of the resource
*/
reload: function (id, options) {
return this.mutex.exec(this._reload.bind(this, id, options));
@@ -925,12 +925,12 @@ var BasicModel = AbstractModel.extend({
* @param {object} [options]
* @param {integer} [options.offset]
* @param {string} [options.field] the field name used as sequence
- * @returns {Deferred<string>} resolves to the local id of the parent
+ * @returns {Promise<string>} resolves to the local id of the parent
*/
resequence: function (modelName, resIDs, parentID, options) {
options = options || {};
if ((resIDs.length <= 1)) {
- return $.when(parentID); // there is nothing to sort
+ return Promise.resolve(parentID); // there is nothing to sort
}
var self = this;
var data = this.localData[parentID];
@@ -952,7 +952,7 @@ var BasicModel = AbstractModel.extend({
if (!wasResequenced) {
// the field on which the resequence was triggered does not
// exist, so no resequence happened server-side
- return $.when();
+ return Promise.resolve();
}
var field = params.field ? params.field : 'sequence';
@@ -1010,7 +1010,7 @@ var BasicModel = AbstractModel.extend({
* be restored later by call discardChanges with option rollback to true
* @param {string} [options.viewType] current viewType. If not set, we will
* assume main viewType from the record
- * @returns {Deferred}
+ * @returns {Promise}
* Resolved with the list of field names (whose value has been modified)
*/
save: function (recordID, options) {
@@ -1054,57 +1054,59 @@ var BasicModel = AbstractModel.extend({
});
}
- var def = $.Deferred();
- var changedFields = Object.keys(changes);
+ var prom = new Promise(function (resolve, reject) {
+ var changedFields = Object.keys(changes);
- if (options.savePoint) {
- return def.resolve(changedFields);
- }
+ if (options.savePoint) {
+ resolve(changedFields);
+ return;
+ }
- def.then(function () {
- record._isDirty = false;
- });
- // in the case of a write, only perform the RPC if there are changes to save
- if (method === 'create' || changedFields.length) {
- var args = method === 'write' ? [[record.data.id], changes] : [changes];
- self._rpc({
- model: record.model,
- method: method,
- args: args,
- context: record.getContext(),
- }).then(function (id) {
- if (method === 'create') {
- record.res_id = id; // create returns an id, write returns a boolean
- record.data.id = id;
- record.offset = record.res_ids.length;
- record.res_ids.push(id);
- record.count++;
- }
+ // in the case of a write, only perform the RPC if there are changes to save
+ if (method === 'create' || changedFields.length) {
+ var args = method === 'write' ? [[record.data.id], changes] : [changes];
+ self._rpc({
+ model: record.model,
+ method: method,
+ args: args,
+ context: record.getContext(),
+ }).then(function (id) {
+ if (method === 'create') {
+ record.res_id = id; // create returns an id, write returns a boolean
+ record.data.id = id;
+ record.offset = record.res_ids.length;
+ record.res_ids.push(id);
+ record.count++;
+ }
- var _changes = record._changes;
+ var _changes = record._changes;
- // Erase changes as they have been applied
- record._changes = {};
+ // Erase changes as they have been applied
+ record._changes = {};
- // Optionally clear the DataManager's cache
- self._invalidateCache(record);
+ // Optionally clear the DataManager's cache
+ self._invalidateCache(record);
- self.unfreezeOrder(record.id);
+ self.unfreezeOrder(record.id);
- // Update the data directly or reload them
- if (shouldReload) {
- self._fetchRecord(record).then(function () {
- def.resolve(changedFields);
- });
- } else {
- _.extend(record.data, _changes);
- def.resolve(changedFields);
- }
- }).fail(def.reject.bind(def));
- } else {
- def.resolve(changedFields);
- }
- return def;
+ // Update the data directly or reload them
+ if (shouldReload) {
+ self._fetchRecord(record).then(function () {
+ resolve(changedFields);
+ });
+ } else {
+ _.extend(record.data, _changes);
+ resolve(changedFields);
+ }
+ }).guardedCatch(reject);
+ } else {
+ resolve(changedFields);
+ }
+ });
+ prom.then(function () {
+ record._isDirty = false;
+ });
+ return prom;
});
},
/**
@@ -1123,7 +1125,7 @@ var BasicModel = AbstractModel.extend({
*
* @param {string} list_id id for the list resource
* @param {string} fieldName valid field name
- * @returns {Deferred}
+ * @returns {Promise}
*/
setSort: function (list_id, fieldName) {
var list = this.localData[list_id];
@@ -1153,7 +1155,7 @@ var BasicModel = AbstractModel.extend({
// sort field is still unknown (i.e. on other pages for example)
return this._fetchUngroupedList(list);
}
- return $.when();
+ return Promise.resolve();
},
/**
* Toggle the active value of given records (to archive/unarchive them)
@@ -1161,7 +1163,7 @@ var BasicModel = AbstractModel.extend({
* @param {Array} recordIDs local ids of the records to (un)archive
* @param {boolean} value false to archive, true to unarchive (value of the active field)
* @param {string} parentID id of the parent resource to reload
- * @returns {Deferred<string>} resolves to the parent id
+ * @returns {Promise<string>} resolves to the parent id
*/
toggleActive: function (recordIDs, value, parentID) {
var self = this;
@@ -1185,7 +1187,7 @@ var BasicModel = AbstractModel.extend({
* data
*
* @param {string} groupId
- * @returns {Deferred<string>} resolves to the group id
+ * @returns {Promise<string>} resolves to the group id
*/
toggleGroup: function (groupId) {
var self = this;
@@ -1196,7 +1198,7 @@ var BasicModel = AbstractModel.extend({
group.res_ids = [];
group.offset = 0;
this._updateParentResIDs(group);
- return $.when(groupId);
+ return Promise.resolve(groupId);
}
if (!group.isOpen) {
group.isOpen = true;
@@ -1206,7 +1208,7 @@ var BasicModel = AbstractModel.extend({
self._updateParentResIDs(group);
});
}
- return $.when(def).then(function () {
+ return Promise.resolve(def).then(function () {
return groupId;
});
}
@@ -1260,7 +1262,7 @@ var BasicModel = AbstractModel.extend({
* If several contexts are found, multiple records are added
* @param {boolean} [options.allowWarning=false] if true, the default record
* operation can complete, even if a warning is raised
- * @returns {Deferred<[string]>} resolves to the new records ids
+ * @returns {Promise<[string]>} resolves to the new records ids
*/
_addX2ManyDefaultRecord: function (list, options) {
var self = this;
@@ -1286,9 +1288,9 @@ var BasicModel = AbstractModel.extend({
makeDefaultRecords.push(self._makeDefaultRecord(list.model, params));
}
- return $.when.apply($, makeDefaultRecords).then(function (){
+ return Promise.all(makeDefaultRecords).then(function (resultIds){
var ids = [];
- _.each(arguments, function (id){
+ _.each(resultIds, function (id){
ids.push(id);
list._changes.push({operation: 'ADD', id: id, position: position, isNew: true});
@@ -1324,7 +1326,7 @@ var BasicModel = AbstractModel.extend({
* @param {boolean} [options.allowWarning=false] if true, change
* operation can complete, even if a warning is raised
* (only supported by X2ManyChange)
- * @returns {Deferred} list of changed fields
+ * @returns {Promise} list of changed fields
*/
_applyChange: function (recordID, changes, options) {
var self = this;
@@ -1354,10 +1356,10 @@ var BasicModel = AbstractModel.extend({
}
if (options.notifyChange === false) {
- return $.Deferred().resolve(_.keys(changes));
+ return Promise.resolve(_.keys(changes));
}
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
var onChangeFields = []; // the fields that have changed and that have an on_change
for (var fieldName in changes) {
field = record.fields[fieldName];
@@ -1368,22 +1370,22 @@ var BasicModel = AbstractModel.extend({
}
}
}
- var onchangeDef = $.Deferred();
- if (onChangeFields.length) {
- self._performOnChange(record, onChangeFields, options.viewType)
+ return new Promise(function (resolve, reject) {
+ if (onChangeFields.length) {
+ self._performOnChange(record, onChangeFields, options.viewType)
.then(function (result) {
delete record._warning;
- onchangeDef.resolve(_.keys(changes).concat(Object.keys(result && result.value || {})));
- }).fail(function () {
+ resolve(_.keys(changes).concat(Object.keys(result && result.value || {})));
+ }).guardedCatch(function () {
self._visitChildren(record, function (elem) {
_.extend(elem, initialData[elem.id]);
});
- onchangeDef.resolve({});
+ resolve({});
});
- } else {
- onchangeDef = $.Deferred().resolve(_.keys(changes));
- }
- return onchangeDef.then(function (fieldNames) {
+ } else {
+ resolve(_.keys(changes));
+ }
+ }).then(function (fieldNames) {
_.each(fieldNames, function (name) {
if (record._changes && record._changes[name] === record.data[name]) {
delete record._changes[name];
@@ -1407,13 +1409,13 @@ var BasicModel = AbstractModel.extend({
* @param {Object} record
* @param {string} fieldName
* @param {Object} [data]
- * @returns {Deferred}
+ * @returns {Promise}
*/
_applyX2OneChange: function (record, fieldName, data) {
var self = this;
if (!data || !data.id) {
record._changes[fieldName] = false;
- return $.when();
+ return Promise.resolve();
}
// here, we check that the many2one really changed. If the res_id is the
@@ -1429,7 +1431,7 @@ var BasicModel = AbstractModel.extend({
}
var relatedRecord = this.localData[relatedID];
if (relatedRecord && (data.id === this.localData[relatedID].res_id)) {
- return $.when();
+ return Promise.resolve();
}
var rel_data = _.pick(data, 'id', 'display_name');
var field = record.fields[fieldName];
@@ -1450,7 +1452,7 @@ var BasicModel = AbstractModel.extend({
rel_data.display_name = result[0][1];
});
}
- return $.when(def).then(function () {
+ return Promise.resolve(def).then(function () {
var rec = self._makeDataPoint({
context: record.context,
data: rel_data,
@@ -1471,7 +1473,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} record
* @param {string} [viewType] current viewType. If not set, we will assume
* main viewType from the record
- * @returns {Deferred}
+ * @returns {Promise}
*/
_applyOnChange: function (values, record, viewType) {
var self = this;
@@ -1634,7 +1636,7 @@ var BasicModel = AbstractModel.extend({
var def = self._readUngroupedList(list).then(function () {
var x2ManysDef = self._fetchX2ManysBatched(list);
var referencesDef = self._fetchReferencesBatched(list);
- return $.when(x2ManysDef, referencesDef);
+ return Promise.all([x2ManysDef, referencesDef]);
});
defs.push(def);
} else {
@@ -1644,7 +1646,7 @@ var BasicModel = AbstractModel.extend({
}
}
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
// inner function that adds a record (based on its res_id) to a list
// dataPoint (used for onchanges that return commands 4 (LINK TO) or
@@ -1680,7 +1682,7 @@ var BasicModel = AbstractModel.extend({
* @param {boolean} [allowWarning=false] if true, change
* operation can complete, even if a warning is raised
* (only supported by the 'CREATE' command.operation)
- * @returns {Deferred}
+ * @returns {Promise}
*/
_applyX2ManyChange: function (record, fieldName, command, viewType, allowWarning) {
if (command.operation === 'TRIGGER_ONCHANGE') {
@@ -1688,7 +1690,7 @@ var BasicModel = AbstractModel.extend({
// there is no need to apply any change on the record (the changes
// have probably been already applied and saved, usecase: many2many
// edition in a dialog)
- return $.when();
+ return Promise.resolve();
}
var self = this;
@@ -1765,10 +1767,10 @@ var BasicModel = AbstractModel.extend({
list_records[record.id].data = record;
self._parseServerData(fieldNames, list, record);
});
- return $.when(
+ return Promise.all([
self._fetchX2ManysBatched(list),
self._fetchReferencesBatched(list)
- );
+ ]);
});
defs.push(def);
}
@@ -1850,10 +1852,10 @@ var BasicModel = AbstractModel.extend({
}),
}, viewType);
}
- return $.when(addDef, removedDef);
+ return Promise.all([addDef, removedDef]);
}
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
// ensure to fetch up to 'limit' records (may be useful if records of
// the current page have been removed)
return self._readUngroupedList(list).then(function () {
@@ -2103,7 +2105,7 @@ var BasicModel = AbstractModel.extend({
* Fetch all name_gets for the many2ones in a group
*
* @param {Object[]} groups a list of object with context and record sub keys
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchMany2OneGroup: function (groups) {
var ids = _.uniq(_.pluck(_.pluck(groups, 'record'), 'res_id'));
@@ -2124,7 +2126,7 @@ var BasicModel = AbstractModel.extend({
* Fetch name_get for a record datapoint.
*
* @param {Object} dataPoint
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchNameGet: function (dataPoint) {
return this._rpc({
@@ -2143,7 +2145,7 @@ var BasicModel = AbstractModel.extend({
* @params {Object} list: must be a datapoint of type list
* (for example: a datapoint representing a x2many)
* @params {string} fieldName: the name of a field of type Many2one or Reference
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchNameGets: function (list, fieldName) {
var self = this;
@@ -2168,7 +2170,7 @@ var BasicModel = AbstractModel.extend({
});
if (!ids.length) {
- return $.when();
+ return Promise.resolve();
}
return this._rpc({
model: model,
@@ -2195,7 +2197,7 @@ var BasicModel = AbstractModel.extend({
* @param {string} [options.viewType] the type of view for which the record
* is fetched (usefull to load the adequate fields), by defaults, uses
* record.viewType
- * @returns {Deferred<Object>} resolves to the record or is rejected in
+ * @returns {Promise<Object>} resolves to the record or is rejected in
* case no id given were valid ids
*/
_fetchRecord: function (record, options) {
@@ -2211,7 +2213,7 @@ var BasicModel = AbstractModel.extend({
})
.then(function (result) {
if (result.length === 0) {
- return $.Deferred().reject();
+ return Promise.reject();
}
result = result[0];
record.data = _.extend({}, record.data, result);
@@ -2220,10 +2222,10 @@ var BasicModel = AbstractModel.extend({
self._parseServerData(fieldNames, record, record.data);
})
.then(function () {
- return $.when(
+ return Promise.all([
self._fetchX2Manys(record, options),
self._fetchReferences(record, options)
- ).then(function () {
+ ]).then(function () {
return self._postprocess(record, options);
});
});
@@ -2234,7 +2236,7 @@ var BasicModel = AbstractModel.extend({
* @private
* @param {Object} record
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReference: function (record, fieldName) {
var self = this;
@@ -2259,7 +2261,7 @@ var BasicModel = AbstractModel.extend({
});
});
}
- return $.when(def);
+ return Promise.resolve(def);
},
/**
* Fetches data for reference fields and assigns these data to newly
@@ -2270,7 +2272,7 @@ var BasicModel = AbstractModel.extend({
* @see _getDataToFetchByModel
* @param {string} model
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReferenceData: function (datapoints, model, fieldName) {
var self = this;
@@ -2307,7 +2309,7 @@ var BasicModel = AbstractModel.extend({
*
* @private
* @param {Object} record
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReferences: function (record, options) {
var self = this;
@@ -2324,7 +2326,7 @@ var BasicModel = AbstractModel.extend({
defs.push(def);
}
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Batch requests for one reference field in list (one request by different
@@ -2333,7 +2335,7 @@ var BasicModel = AbstractModel.extend({
* @see _fetchReferencesBatched
* @param {Object} list
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReferenceBatched: function (list, fieldName) {
var self = this;
@@ -2347,13 +2349,13 @@ var BasicModel = AbstractModel.extend({
defs.push(self._fetchReferenceData(datapoints, model, fieldName));
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Batch requests for references for datapoint of type list.
*
* @param {Object} list
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReferencesBatched: function (list) {
var defs = [];
@@ -2364,7 +2366,7 @@ var BasicModel = AbstractModel.extend({
defs.push(this._fetchReferenceBatched(list, fieldNames[i]));
}
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Batch reference requests for all records in list.
@@ -2372,7 +2374,7 @@ var BasicModel = AbstractModel.extend({
* @see _fetchReferencesSingleBatch
* @param {Object} list a valid resource object
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReferenceSingleBatch: function (list, fieldName) {
var self = this;
@@ -2390,14 +2392,14 @@ var BasicModel = AbstractModel.extend({
defs.push(self._fetchReferenceData(datapoints, model, fieldName));
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Batch requests for all reference field in list's children.
* Called by _readGroup to make only one 'name_get' rpc by fieldName.
*
* @param {Object} list a valid resource object
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchReferencesSingleBatch: function (list) {
var defs = [];
@@ -2408,7 +2410,7 @@ var BasicModel = AbstractModel.extend({
defs.push(this._fetchReferenceSingleBatch(list, fieldNames[fIndex]));
}
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Fetch model data from server, relationally to fieldName and resulted
@@ -2420,7 +2422,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} toFetch a list of records and res_ids,
* @see _getDataToFetch
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchRelatedData: function (list, toFetch, fieldName) {
var self = this;
@@ -2431,7 +2433,7 @@ var BasicModel = AbstractModel.extend({
var fieldInfo = list.fieldsInfo[list.viewType][fieldName];
if (!ids.length || fieldInfo.__no_fetch) {
- return $.when();
+ return Promise.resolve();
}
var def;
@@ -2445,7 +2447,7 @@ var BasicModel = AbstractModel.extend({
context: list.getContext() || {},
});
} else {
- def = $.when(_.map(ids, function (id) {
+ def = Promise.resolve(_.map(ids, function (id) {
return {id:id};
}));
}
@@ -2465,10 +2467,10 @@ var BasicModel = AbstractModel.extend({
* This method is currently only called by _makeDefaultRecord, it should be
* called by the onchange methods at some point.
*
- * @todo fix bug: returns a list of deferred, not a deferred
+ * @todo fix bug: returns a list of promise, not a promise
*
* @param {Object} record a valid resource object
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchRelationalData: function (record) {
var self = this;
@@ -2496,7 +2498,7 @@ var BasicModel = AbstractModel.extend({
return [elem.record.model, JSON.stringify(elem.context)].join();
});
- return $.when.apply($, _.map(groups, this._fetchMany2OneGroup.bind(this)));
+ return Promise.all(_.map(groups, this._fetchMany2OneGroup.bind(this)));
},
/**
* Check the AbstractField specializations that are (will be) used by the
@@ -2507,15 +2509,15 @@ var BasicModel = AbstractModel.extend({
*
* @param {Object} record - an element from the localData
* @param {Object} options
- * @returns {Deferred<Array>}
- * The deferred is resolved with an array containing the names of
+ * @returns {Promise<Array>}
+ * The promise is resolved with an array containing the names of
* the field whose special data has been changed.
*/
_fetchSpecialData: function (record, options) {
var self = this;
var specialFieldNames = [];
var fieldNames = (options && options.fieldNames) || record.getFieldNames();
- return $.when.apply($, _.map(fieldNames, function (name) {
+ return Promise.all(_.map(fieldNames, function (name) {
var viewType = (options && options.viewType) || record.viewType;
var fieldInfo = record.fieldsInfo[viewType][name] || {};
var Widget = fieldInfo.Widget;
@@ -2541,16 +2543,16 @@ var BasicModel = AbstractModel.extend({
* @param {Object} fieldInfo
* @param {string[]} [fieldsToRead] - the m2os fields to read (id and
* display_name are automatic).
- * @returns {Deferred<any>}
- * The deferred is resolved with the fetched special data. If this
+ * @returns {Promise<any>}
+ * The promise is resolved with the fetched special data. If this
* data is the same as the previously fetched one (for the given
- * parameters), no RPC is done and the deferred is resolved with
+ * parameters), no RPC is done and the promise is resolved with
* the undefined value.
*/
_fetchSpecialMany2ones: function (record, fieldName, fieldInfo, fieldsToRead) {
var field = record.fields[fieldName];
if (field.type !== "many2one") {
- return $.when();
+ return Promise.resolve();
}
var context = record.getContext({fieldName: fieldName});
@@ -2571,7 +2573,7 @@ var BasicModel = AbstractModel.extend({
domain: domain,
});
if (!hasChanged) {
- return $.when();
+ return Promise.resolve();
}
var self = this;
@@ -2607,16 +2609,16 @@ var BasicModel = AbstractModel.extend({
*
* @param {Object} record - an element from the localData
* @param {Object} fieldName - the name of the field
- * @returns {Deferred<any>}
- * The deferred is resolved with the fetched special data. If this
+ * @returns {Promise<any>}
+ * The promise is resolved with the fetched special data. If this
* data is the same as the previously fetched one (for the given
- * parameters), no RPC is done and the deferred is resolved with
+ * parameters), no RPC is done and the promise is resolved with
* the undefined value.
*/
_fetchSpecialRelation: function (record, fieldName) {
var field = record.fields[fieldName];
if (!_.contains(["many2one", "many2many", "one2many"], field.type)) {
- return $.when();
+ return Promise.resolve();
}
var context = record.getContext({fieldName: fieldName});
@@ -2628,7 +2630,7 @@ var BasicModel = AbstractModel.extend({
domain: domain,
});
if (!hasChanged) {
- return $.when();
+ return Promise.resolve();
}
return this._rpc({
@@ -2645,7 +2647,7 @@ var BasicModel = AbstractModel.extend({
* @private
* @param {Object} record - an element from the localData
* @param {Object} fieldName - the name of the field
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchSpecialReference: function (record, fieldName) {
var def;
@@ -2655,7 +2657,7 @@ var BasicModel = AbstractModel.extend({
// needs to be fetched a posteriori
def = this._fetchReference(record, fieldName);
}
- return $.when(def);
+ return Promise.resolve(def);
},
/**
* Fetches all the m2o records associated to the given fieldName. If the
@@ -2666,10 +2668,10 @@ var BasicModel = AbstractModel.extend({
* @param {Object} record - an element from the localData
* @param {Object} fieldName - the name of the field
* @param {Object} fieldInfo
- * @returns {Deferred<any>}
- * The deferred is resolved with the fetched special data. If this
+ * @returns {Promise<any>}
+ * The promise is resolved with the fetched special data. If this
* data is the same as the previously fetched one (for the given
- * parameters), no RPC is done and the deferred is resolved with
+ * parameters), no RPC is done and the promise is resolved with
* the undefined value.
*/
_fetchSpecialStatus: function (record, fieldName, fieldInfo) {
@@ -2689,13 +2691,14 @@ var BasicModel = AbstractModel.extend({
* @param {Object} record - an element from the localData
* @param {Object} fieldName - the name of the field
* @param {Object} fieldInfo
- * @returns {Deferred<any>}
- * The deferred is resolved with the fetched special data. If this
+ * @returns {Promise<any>}
+ * The promise is resolved with the fetched special data. If this
* data is the same as the previously fetched one (for the given
- * parameters), no RPC is done and the deferred is resolved with
+ * parameters), no RPC is done and the promise is resolved with
* the undefined value.
*/
_fetchSpecialDomain: function (record, fieldName, fieldInfo) {
+ var self = this;
var context = record.getContext({fieldName: fieldName});
var domainModel = fieldInfo.options.model;
@@ -2711,34 +2714,37 @@ var BasicModel = AbstractModel.extend({
domainValue: domainValue,
});
if (!hasChanged) {
- return $.when();
+ return Promise.resolve();
} else if (!domainModel) {
- return $.when({
+ return Promise.resolve({
model: domainModel,
nbRecords: 0,
});
}
- var def = $.Deferred();
- var evalContext = this._getEvalContext(record);
- this._rpc({
+ return new Promise(function (resolve) {
+ var evalContext = self._getEvalContext(record);
+ self._rpc({
model: domainModel,
method: 'search_count',
args: [Domain.prototype.stringToArray(domainValue, evalContext)],
context: context
})
- .then(_.identity, function (error, e) {
- e.preventDefault(); // prevent traceback (the search_count might be intended to break)
- return false;
- })
- .always(function (nbRecords) {
- def.resolve({
+ .then(function (nbRecords) {
+ resolve({
model: domainModel,
nbRecords: nbRecords,
});
+ })
+ .guardedCatch(function (reason) {
+ var e = reason.event;
+ e.preventDefault(); // prevent traceback (the search_count might be intended to break)
+ resolve({
+ model: domainModel,
+ nbRecords: 0,
+ });
});
-
- return def;
+ });
},
/**
* Fetch all data in a ungrouped list
@@ -2748,7 +2754,7 @@ var BasicModel = AbstractModel.extend({
* @param {boolean} [options.enableRelationalFetch=true] if false, will not
* fetch x2m and relational data (that will be done by _readGroup in this
* case).
- * @returns {Deferred<Object>} resolves to the fetched list
+ * @returns {Promise<Object>} resolves to the fecthed list
*/
_fetchUngroupedList: function (list, options) {
options = _.defaults(options || {}, {enableRelationalFetch: true});
@@ -2762,7 +2768,7 @@ var BasicModel = AbstractModel.extend({
var defs = _.map(many2ones, function (name) {
return self._fetchNameGets(list, name);
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
}
});
} else {
@@ -2770,10 +2776,10 @@ var BasicModel = AbstractModel.extend({
}
return def.then(function () {
if (options.enableRelationalFetch) {
- return $.when(
- self._fetchX2ManysBatched(list, options),
- self._fetchReferencesBatched(list, options)
- );
+ return Promise.all([
+ self._fetchX2ManysBatched(list),
+ self._fetchReferencesBatched(list)
+ ]);
}
}).then(function () {
return list;
@@ -2785,7 +2791,7 @@ var BasicModel = AbstractModel.extend({
* @see _fetchX2ManysBatched
* @param {Object} list
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchX2ManyBatched: function (list, fieldName) {
list = this._applyX2ManyOperations(list);
@@ -2810,7 +2816,7 @@ var BasicModel = AbstractModel.extend({
* @param {string} [options.viewType] the type of view for which the main
* record is fetched (useful to load the adequate fields), by defaults,
* uses record.viewType
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchX2Manys: function (record, options) {
var self = this;
@@ -2845,22 +2851,22 @@ var BasicModel = AbstractModel.extend({
record.data[fieldName] = list.id;
if (!fieldInfo.__no_fetch) {
var def = self._readUngroupedList(list).then(function () {
- return $.when(
+ return Promise.all([
self._fetchX2ManysBatched(list),
self._fetchReferencesBatched(list)
- );
+ ]);
});
defs.push(def);
}
}
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* batch request for x2ms for datapoint of type list
*
* @param {Object} list
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchX2ManysBatched: function (list) {
var defs = [];
@@ -2871,7 +2877,7 @@ var BasicModel = AbstractModel.extend({
defs.push(this._fetchX2ManyBatched(list, fieldNames[i]));
}
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* For a non-static list, batches requests for all its sublists' records.
@@ -2881,7 +2887,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} list a valid resource object, its data must be another
* list containing records
* @param {string} fieldName
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchX2ManySingleBatch: function (list, fieldName) {
var self = this;
@@ -2904,7 +2910,7 @@ var BasicModel = AbstractModel.extend({
* Called by _readGroup to make only one 'read' rpc by fieldName.
*
* @param {Object} list a valid resource object
- * @returns {Deferred}
+ * @returns {Promise}
*/
_fetchX2ManysSingleBatch: function (list) {
var defs = [];
@@ -2915,7 +2921,7 @@ var BasicModel = AbstractModel.extend({
defs.push(this._fetchX2ManySingleBatch(list, fieldNames[i]));
}
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Generates an object mapping field names to their changed value in a given
@@ -3584,12 +3590,12 @@ var BasicModel = AbstractModel.extend({
* @param {boolean} [options.keepEmptyGroups=false] if set, the groups not
* present in the read_group anymore (empty groups) will stay in the
* datapoint (used to mimic the kanban renderer behaviour for example)
- * @returns {Deferred}
+ * @returns {Promise}
*/
_load: function (dataPoint, options) {
if (options && options.onlyGroups &&
!(dataPoint.type === 'list' && dataPoint.groupedBy.length)) {
- return $.when(dataPoint);
+ return Promise.resolve(dataPoint);
}
if (dataPoint.type === 'record') {
@@ -3733,7 +3739,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} params.fields contains the description of each field
* @param {Object} params.context the context for the new record
* @param {string} params.viewType the key in fieldsInfo of the fields to load
- * @returns {Deferred<string>} resolves to the id for the created resource
+ * @returns {Promise<string>} resolves to the id for the created resource
*/
_makeDefaultRecord: function (modelName, params) {
var self = this;
@@ -3787,16 +3793,19 @@ var BasicModel = AbstractModel.extend({
return self.applyDefaultValues(record.id, result, {fieldNames: fieldNames})
.then(function () {
- var def = $.Deferred();
- self._performOnChange(record, fields_key).always(function () {
- if (record._warning) {
- if (params.allowWarning) {
- delete record._warning;
- } else {
- def.reject();
+ var def = new Promise(function (resolve, reject) {
+ var always = function () {
+ if (record._warning) {
+ if (params.allowWarning) {
+ delete record._warning;
+ } else {
+ reject();
+ }
}
- }
- def.resolve();
+ resolve();
+ };
+ self._performOnChange(record, fields_key)
+ .then(always).guardedCatch(always);
});
return def;
})
@@ -3865,13 +3874,13 @@ var BasicModel = AbstractModel.extend({
* @param {string[]} fields changed fields
* @param {string} [viewType] current viewType. If not set, we will assume
* main viewType from the record
- * @returns {Deferred}
+ * @returns {Promise}
*/
_performOnChange: function (record, fields, viewType) {
var self = this;
var onchangeSpec = this._buildOnchangeSpecs(record, viewType);
if (!onchangeSpec) {
- return $.when();
+ return Promise.resolve();
}
var idList = record.data.id ? [record.data.id] : [];
var options = {
@@ -3925,7 +3934,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {Object} [options.viewType] current viewType. If not set, we will
* assume main viewType from the record
- * @returns {Deferred<Object>} resolves to the finished resource
+ * @returns {Promise<Object>} resolves to the finished resource
*/
_postprocess: function (record, options) {
var self = this;
@@ -3954,7 +3963,7 @@ var BasicModel = AbstractModel.extend({
defs.push(this._fetchSpecialData(record, options));
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
return record;
});
},
@@ -3975,7 +3984,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {string} [options.viewType] current viewType. If not set, we will
* assume main viewType from the record
- * @returns {Deferred}
+ * @returns {Promise}
*/
_processX2ManyCommands: function (record, fieldName, commands, options) {
var self = this;
@@ -4076,10 +4085,10 @@ var BasicModel = AbstractModel.extend({
x2manyList._changes.push({operation: 'ADD', resID: res_id});
});
var def = self._readUngroupedList(x2manyList).then(function () {
- return $.when(
+ return Promise.all([
self._fetchX2ManysBatched(x2manyList),
self._fetchReferencesBatched(x2manyList)
- );
+ ]);
});
defs.push(def);
}
@@ -4090,7 +4099,7 @@ var BasicModel = AbstractModel.extend({
defs.push(self._fetchNameGets(x2manyList, name));
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Reads data from server for all missing fields.
@@ -4099,7 +4108,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} list a valid resource object
* @param {interger[]} resIDs
* @param {string[]} fieldNames to check and read if missing
- * @returns {Deferred<Object>}
+ * @returns {Promise<Object>}
*/
_readMissingFields: function (list, resIDs, fieldNames) {
var self = this;
@@ -4128,7 +4137,7 @@ var BasicModel = AbstractModel.extend({
context: list.getContext(),
});
} else {
- def = $.when(_.map(missingIDs, function (id) {
+ def = Promise.resolve(_.map(missingIDs, function (id) {
return {id:id};
}));
}
@@ -4174,7 +4183,7 @@ var BasicModel = AbstractModel.extend({
*
* @param {Object} list valid resource object
* @param {Object} [options] @see _load
- * @returns {Deferred<Object>} resolves to the fetched group object
+ * @returns {Promise<Object>} resolves to the fetched group object
*/
_readGroup: function (list, options) {
var self = this;
@@ -4285,20 +4294,20 @@ var BasicModel = AbstractModel.extend({
});
}
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function (groups) {
if (!options || !options.onlyGroups) {
// generate the res_ids of the main list, being the concatenation
// of the fetched res_ids in each group
- list.res_ids = _.flatten(_.map(arguments, function (group) {
+ list.res_ids = _.flatten(_.map(groups, function (group) {
return group ? group.res_ids : [];
}));
}
return list;
}).then(function () {
- var fetchingDefs = [];
- fetchingDefs.push(self._fetchX2ManysSingleBatch(list));
- fetchingDefs.push(self._fetchReferencesSingleBatch(list));
- return $.when.apply($, fetchingDefs).then(function () {
+ return Promise.all([
+ self._fetchX2ManysSingleBatch(list),
+ self._fetchReferencesSingleBatch(list)
+ ]).then(function () {
return list;
});
});
@@ -4309,11 +4318,11 @@ var BasicModel = AbstractModel.extend({
* instead of a /search_read.
*
* @param {Object} list a valid resource object
- * @returns {Deferred<Object>} resolves to the fetched list object
+ * @returns {Promise<Object>} resolves to the fetched list object
*/
_readUngroupedList: function (list) {
var self = this;
- var def = $.when();
+ var def = Promise.resolve();
// generate the current count and res_ids list by applying the changes
list = this._applyX2ManyOperations(list);
@@ -4375,7 +4384,7 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {boolean} [options.keepChanges=false] if true, doesn't discard the
* changes on the record before reloading it
- * @returns {Deferred<string>} resolves to the id of the resource
+ * @returns {Promise<string>} resolves to the id of the resource
*/
_reload: function (id, options) {
options = options || {};
@@ -4464,7 +4473,7 @@ var BasicModel = AbstractModel.extend({
* /search_read because the data may not be static (for ex, a list view).
*
* @param {Object} list
- * @returns {Deferred}
+ * @returns {Promise}
*/
_searchReadUngroupedList: function (list) {
var self = this;
diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js
index 27821081f96..2bc63f47073 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -61,13 +61,13 @@ var BasicRenderer = AbstractRenderer.extend({
* before notifying them).
*
* @param {string} recordID
- * @return {Deferred}
+ * @return {Promise}
*/
commitChanges: function (recordID) {
var defs = _.map(this.allFieldWidgets[recordID], function (widget) {
return widget.commitChanges();
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Updates the internal state of the renderer to the new state. By default,
@@ -86,7 +86,7 @@ var BasicRenderer = AbstractRenderer.extend({
* @param {string} id
* @param {string[]} fields
* @param {OdooEvent} ev
- * @returns {Deferred<AbstractField[]>} resolved with the list of widgets
+ * @returns {Promise<AbstractField[]>} resolved with the list of widgets
* that have been reset
*/
confirmChange: function (state, id, fields, ev) {
@@ -118,7 +118,7 @@ var BasicRenderer = AbstractRenderer.extend({
// state before evaluation
defs.push(this._updateAllModifiers(record));
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
return resetWidgets;
});
},
@@ -295,7 +295,7 @@ var BasicRenderer = AbstractRenderer.extend({
*
* @private
* @param {AbstractField} widget
- * @returns {boolean|Deferred<boolean>} @see AbstractField.isValid
+ * @returns {boolean|Promise<boolean>} @see AbstractField.isValid
*/
_canWidgetBeSaved: function (widget) {
var modifiers = this._getEvaluatedModifiers(widget.__node, widget.record);
@@ -386,6 +386,7 @@ var BasicRenderer = AbstractRenderer.extend({
* @param {Object} node the attrs coming from the arch
*/
_postProcessField: function (widget, node) {
+ this._handleAttributes(widget.$el, node);
},
/**
* Registers or updates the modifiers data associated to the given node.
@@ -503,7 +504,7 @@ var BasicRenderer = AbstractRenderer.extend({
* Render the view
*
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
var oldAllFieldWidgets = this.allFieldWidgets;
@@ -520,7 +521,7 @@ var BasicRenderer = AbstractRenderer.extend({
/**
* Instantiates the appropriate AbstractField specialization for the given
* node and prepares its rendering and addition to the DOM. Indeed, the
- * rendering of the widget will be started and the associated deferred will
+ * rendering of the widget will be started and the associated promise will
* be added to the 'defs' attribute. This is supposed to be created and
* deleted by the calling code if necessary.
*
@@ -559,28 +560,31 @@ var BasicRenderer = AbstractRenderer.extend({
widget.__node = node; // TODO get rid of this if possible one day
- // Prepare widget rendering and save the related deferred
+ // Prepare widget rendering and save the related promise
var def = widget._widgetRenderAndInsert(function () {});
- var async = def.state() === 'pending';
- var $el = async ? $('<div>') : widget.$el;
- if (async) {
- this.defs.push(def);
- }
+ var $el = $('<div>');
+
+ this.defs.push(def);
// Update the modifiers registration by associating the widget and by
// giving the modifiers options now (as the potential callback is
// associated to new widget)
var self = this;
def.then(function () {
- if (async) {
- $el.replaceWith(widget.$el);
- }
+ // when the caller of renderFieldWidget uses something like
+ // this.renderFieldWidget(...).addClass(...), the class is added on
+ // the temporary div and not on the actual element that will be
+ // rendered. As we do not return a promise and some callers cannot
+ // wait for this.defs, we copy those attributes to the final element.
+ widget.$el.attr($el.getAttributes());
+
+ $el.replaceWith(widget.$el);
self._registerModifiers(node, record, widget, {
callback: function (element, modifiers, record) {
element.$el.toggleClass('o_field_empty', !!(
- record.data.id
- && (modifiers.readonly || mode === 'readonly')
- && !element.widget.isSet()
+ record.data.id &&
+ (modifiers.readonly || mode === 'readonly') &&
+ !element.widget.isSet()
));
},
keepBaseMode: !!options.keepBaseMode,
@@ -618,10 +622,10 @@ var BasicRenderer = AbstractRenderer.extend({
* possible.
*
* @abstract
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderView: function () {
- return $.when();
+ return Promise.resolve();
},
/**
* Instantiate custom widgets
@@ -637,22 +641,17 @@ var BasicRenderer = AbstractRenderer.extend({
this.widgets.push(widget);
- // Prepare widget rendering and save the related deferred
+ // Prepare widget rendering and save the related promise
var def = widget._widgetRenderAndInsert(function () {});
- var async = def.state() === 'pending';
- if (async) {
- this.defs.push(def);
- }
- var $el = async ? $('<div>') : widget.$el;
+ this.defs.push(def);
+ var $el = $('<div>');
var self = this;
def.then(function () {
self._handleAttributes(widget.$el, node);
self._registerModifiers(node, record, widget);
widget.$el.addClass('o_widget');
- if (async) {
- $el.replaceWith(widget.$el);
- }
+ $el.replaceWith(widget.$el);
});
return $el;
@@ -709,7 +708,7 @@ var BasicRenderer = AbstractRenderer.extend({
*
* @private
* @param {Object} record
- * @returns {Deferred} resolved once finished
+ * @returns {Promise} resolved once finished
*/
_updateAllModifiers: function (record) {
var self = this;
@@ -722,7 +721,7 @@ var BasicRenderer = AbstractRenderer.extend({
});
delete this.defs;
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
//--------------------------------------------------------------------------
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 313bed44708..008bbcf8775 100644
--- a/addons/web/static/src/js/views/basic/basic_view.js
+++ b/addons/web/static/src/js/views/basic/basic_view.js
@@ -93,7 +93,7 @@ var BasicView = AbstractView.extend({
*
* @override
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadData: function (model) {
if (this.recordID) {
@@ -195,7 +195,7 @@ var BasicView = AbstractView.extend({
}
});
}
- return $.when(def).then(function () {
+ return Promise.resolve(def).then(function () {
return model.get(record.id);
});
}
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index 0934ae462b3..fa41706de2b 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -143,7 +143,7 @@ var CalendarController = AbstractController.extend({
*
* @private
* @param {string} to either 'prev', 'next' or 'today'
- * @returns {Deferred}
+ * @returns {Promise}
*/
_move: function (to) {
this.model[to]();
@@ -153,7 +153,7 @@ var CalendarController = AbstractController.extend({
* @private
* @param {Object} record
* @param {integer} record.id
- * @returns {Deferred}
+ * @returns {Promise}
*/
_updateRecord: function (record) {
return this.model.updateRecord(record).then(this.reload.bind(this, {}));
@@ -388,18 +388,18 @@ var CalendarController = AbstractController.extend({
self.quick.destroy();
self.quick = null;
self.reload();
+ self.quickCreating = false;
})
- .fail(function (error, errorEvent) {
+ .guardedCatch(function (result) {
+ var errorEvent = result.event;
// This will occurs if there are some more fields required
// Preventdefaulting the error event will prevent the traceback window
errorEvent.preventDefault();
event.data.options.disableQuickCreate = true;
event.data.data.on_save = self.quick.destroy.bind(self.quick);
self._onOpenCreate(event.data);
- })
- .always(function () {
self.quickCreating = false;
- });
+ })
},
/**
* Called when we want to open or close the sidebar.
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index aaf52057ad6..9d8cf04f30f 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -177,7 +177,7 @@ return AbstractModel.extend({
/**
* @override
* @param {any} params
- * @returns {Deferred}
+ * @returns {Promise}
*/
load: function (params) {
var self = this;
@@ -199,15 +199,18 @@ return AbstractModel.extend({
// fields to display color, e.g.: user_id.partner_id
this.fieldColor = params.fieldColor;
- if (!this.preload_def) {
- this.preload_def = $.Deferred();
- $.when(
- this._rpc({model: this.modelName, method: 'check_access_rights', args: ["write", false]}),
- this._rpc({model: this.modelName, method: 'check_access_rights', args: ["create", false]}))
- .then(function (write, create) {
- self.write_right = write;
- self.create_right = create;
- self.preload_def.resolve();
+ if (!this.preloadPromise) {
+ this.preloadPromise = new Promise(function (resolve, reject) {
+ Promise.all([
+ self._rpc({model: self.modelName, method: 'check_access_rights', args: ["write", false]}),
+ self._rpc({model: self.modelName, method: 'check_access_rights', args: ["create", false]})
+ ]).then(function (result) {
+ var write = result[0];
+ var create = result[1];
+ self.write_right = write;
+ self.create_right = create;
+ resolve();
+ }).guardedCatch(reject);
});
}
@@ -229,7 +232,7 @@ return AbstractModel.extend({
}
});
- return this.preload_def.then(this._loadCalendar.bind(this));
+ return this.preloadPromise.then(this._loadCalendar.bind(this));
},
/**
* Move the current date range to the next period
@@ -247,7 +250,7 @@ return AbstractModel.extend({
* @override
* @param {Object} [params.context]
* @param {Array} [params.domain]
- * @returns {Deferred}
+ * @returns {Promise}
*/
reload: function (handle, params) {
if (params.domain) {
@@ -308,7 +311,7 @@ return AbstractModel.extend({
/**
* @param {Object} record
* @param {integer} record.id
- * @returns {Deferred}
+ * @returns {Promise}
*/
updateRecord: function (record) {
// Cannot modify actual name yet
@@ -430,7 +433,7 @@ return AbstractModel.extend({
},
/**
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadCalendar: function () {
var self = this;
@@ -439,7 +442,7 @@ return AbstractModel.extend({
var defs = _.map(this.data.filters, this._loadFilter.bind(this));
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
return self._rpc({
model: self.modelName,
method: 'search_read',
@@ -450,10 +453,10 @@ return AbstractModel.extend({
.then(function (events) {
self._parseServerData(events);
self.data.data = _.map(events, self._recordToCalendarEvent.bind(self));
- return $.when(
+ return Promise.all([
self._loadColors(self.data, self.data.data),
self._loadRecordsToFilters(self.data, self.data.data)
- );
+ ]);
});
});
},
@@ -461,7 +464,7 @@ return AbstractModel.extend({
* @private
* @param {any} element
* @param {any} events
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadColors: function (element, events) {
if (this.fieldColor) {
@@ -472,16 +475,16 @@ return AbstractModel.extend({
});
this.model_color = this.fields[fieldName].relation || element.model;
}
- return $.Deferred().resolve();
+ return Promise.resolve();
},
/**
* @private
* @param {any} filter
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadFilter: function (filter) {
if (!filter.write_model) {
- return $.when();
+ return Promise.resolve();
}
var field = this.fields[filter.fieldName];
@@ -540,7 +543,7 @@ return AbstractModel.extend({
* @private
* @param {any} element
* @param {any} events
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadRecordsToFilters: function (element, events) {
var self = this;
@@ -608,7 +611,7 @@ return AbstractModel.extend({
to_read[model] = _.object(res);
}));
});
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
_.each(self.data.filters, function (filter) {
if (filter.write_model) {
return;
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index 0ea7d60b1ce..c66b7bcc665 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -85,7 +85,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
});
defs.push(def);
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
@@ -193,7 +193,7 @@ return AbstractRenderer.extend({
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this._initSidebar();
@@ -460,7 +460,7 @@ return AbstractRenderer.extend({
*
* @override method from AbstractRenderer
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
var $calendar = this.$calendar;
@@ -504,7 +504,7 @@ return AbstractRenderer.extend({
this.$sidebar_container.toggleClass('o_sidebar_hidden', fullWidth);
this.$sidebar.toggleClass('o_hidden', fullWidth);
- this._renderFilters();
+ var filterProm = this._renderFilters();
this.$calendar.appendTo('body');
if (scrollTop) {
this.$calendar.fullCalendar('reinitView');
@@ -514,7 +514,7 @@ return AbstractRenderer.extend({
this._renderEvents();
this.$calendar.prependTo(this.$('.o_calendar_view'));
- return this._super.apply(this, arguments);
+ return Promise.all([filterProm, this._super.apply(this, arguments)]);
},
/**
* Render all events
@@ -529,25 +529,47 @@ return AbstractRenderer.extend({
* Render all filters
*
* @private
+ * @returns {Promise} resolved when all filters have been rendered
*/
_renderFilters: function () {
- var self = this;
_.each(this.filters || (this.filters = []), function (filter) {
filter.destroy();
});
if (this.state.fullWidth) {
return;
}
- _.each(this.state.filters, function (options) {
+ return this._renderFiltersOneByOne();
+ },
+ /**
+ * Renders each filter one by one, waiting for the first filter finished to
+ * be rendered and appended to render the next one.
+ * We need to do like this since render a filter is asynchronous, we don't
+ * know which one will be appened at first and we want tp force them to be
+ * rendered in order.
+ *
+ * @param {number} filterIndex if not set, 0 by default
+ * @returns {Promise} resolved when all filters have been rendered
+ */
+ _renderFiltersOneByOne: function (filterIndex) {
+ filterIndex = filterIndex || 0;
+ var arrFilters = _.toArray(this.state.filters);
+ var prom;
+ if (filterIndex < arrFilters.length) {
+ var options = arrFilters[filterIndex];
if (!_.find(options.filters, function (f) {return f.display == null || f.display;})) {
return;
}
- options.getColor = self.getColor.bind(self);
- options.fields = self.state.fields;
+
+ var self = this;
+ options.getColor = this.getColor.bind(this);
+ options.fields = this.state.fields;
var filter = new SidebarFilter(self, options);
- filter.appendTo(self.$sidebar);
- self.filters.push(filter);
- });
+ prom = filter.appendTo(this.$sidebar).then(function () {
+ return self._renderFiltersOneByOne(filterIndex + 1);
+ });
+ this.filters.push(filter);
+ }
+ return Promise.resolve(prom);
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_controller.js b/addons/web/static/src/js/views/control_panel/control_panel_controller.js
index 40b1c45b9c7..913c226ba85 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_controller.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_controller.js
@@ -63,14 +63,14 @@ var ControlPanelController = mvc.Controller.extend({
},
/**
* @param {Object} state a ControlPanelModel state
- * @returns {Deferred<Object>} the result of `getSearchState`
+ * @returns {Promise<Object>} the result of `getSearchState`
*/
importState: function (state) {
var defs = [];
this.model.importState(state);
defs.push(this.getSearchQuery());
defs.push(this.renderer.updateState(this.model.get()));
- return $.when(defs).then(function (defsResults) {
+ return Promise.all(defs).then(function (defsResults) {
return defsResults[0];
});
},
@@ -127,7 +127,7 @@ var ControlPanelController = mvc.Controller.extend({
},
/**
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_reportNewQueryAndRender: function () {
this.trigger_up('search', this.model.getQuery());
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_model.js b/addons/web/static/src/js/views/control_panel/control_panel_model.js
index e59dbfb1036..ad0d80de7a3 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_model.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_model.js
@@ -325,7 +325,7 @@ var ControlPanelModel = mvc.Model.extend({
* @param {string[]} [params.searchMenuTypes=[]]
* @param {Object} [params.timeRanges]
* @param {boolean} [params.withSearchBar]
- * @returns {Deferred}
+ * @returns {Promise}
*/
load: function (params) {
var self = this;
@@ -337,11 +337,11 @@ var ControlPanelModel = mvc.Model.extend({
// and the info comming from arch put in params.groups (if any)
// will be lost. This is not a problem because the state
// won't be use elsewhere.
- return $.when();
+ return Promise.resolve();
}
if (params.initialState) {
this.importState(params.initialState);
- return $.when();
+ return Promise.resolve();
} else {
var groups = params.groups || [];
groups.forEach(function (group) {
@@ -351,7 +351,7 @@ var ControlPanelModel = mvc.Model.extend({
this._createEmptyGroup('groupBy');
}
this._createGroupOfTimeRanges();
- return $.when.apply($, self._loadSearchDefaults()).then(function () {
+ return Promise.all(self._loadSearchDefaults()).then(function () {
return self._loadFavorites().then(function () {
if (self.query.length === 0) {
self._activateDefaultFilters();
@@ -910,7 +910,7 @@ var ControlPanelModel = mvc.Model.extend({
* if this.activateDefaultFavorite is true.
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadFavorites: function () {
var self = this;
@@ -980,7 +980,7 @@ var ControlPanelModel = mvc.Model.extend({
* is asynchronous.
*
* @private
- * @returns {Deferred[]}
+ * @returns {Promise[]}
*/
_loadSearchDefaults: function () {
var self = this;
@@ -1044,7 +1044,7 @@ var ControlPanelModel = mvc.Model.extend({
*
* @private
* @param {Object} favorite
- * @returns {Deferred}
+ * @returns {Promise}
*/
_saveQuery: function (favorite) {
var self = this;
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_renderer.js b/addons/web/static/src/js/views/control_panel/control_panel_renderer.js
index fd9037cdf6f..a1da2925eb6 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_renderer.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_renderer.js
@@ -86,7 +86,7 @@ var ControlPanelRenderer = Renderer.extend({
var superDef = this._super.apply(this, arguments);
var searchDef = this._renderSearch();
- return $.when(superDef, searchDef).then(function () {
+ return Promise.all([superDef, searchDef]).then(function () {
self._setSearchMenusVisibility();
});
},
@@ -148,7 +148,7 @@ var ControlPanelRenderer = Renderer.extend({
* Update the state of the renderer state. It retriggers a full rerendering.
*
* @param {Object} state
- * @returns {Deferred}
+ * @returns {Promise}
*/
updateState: function (state) {
this.state = state;
@@ -261,7 +261,7 @@ var ControlPanelRenderer = Renderer.extend({
* Renderer the search bar and the search menus
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderSearch: function () {
var defs = [];
@@ -274,11 +274,11 @@ var ControlPanelRenderer = Renderer.extend({
if (this.withSearchBar) {
defs.push(this._renderSearchBar());
}
- return $.when(this, defs).then(this._focusSearchInput.bind(this));
+ return Promise.all(defs).then(this._focusSearchInput.bind(this));
},
/**
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderSearchBar: function () {
// TODO: might need a reload instead of a destroy/instantiate
@@ -315,7 +315,7 @@ var ControlPanelRenderer = Renderer.extend({
*
* @private
* @param {string} menuType
- * @returns {Deferred}
+ * @returns {Promise}
*/
_setupMenu: function (menuType) {
var Menu;
@@ -345,7 +345,7 @@ var ControlPanelRenderer = Renderer.extend({
* Instantiate the search menu determined by this.searchMenuTypes.
*
* @private
- * @returns {Deferred[]}
+ * @returns {Promise[]}
*/
_setupMenus: function () {
this.$subMenus = this._getSubMenusPlace();
diff --git a/addons/web/static/src/js/views/control_panel/search/filter_menu.js b/addons/web/static/src/js/views/control_panel/search/filter_menu.js
index 04f333b37c4..4ded35cfecc 100644
--- a/addons/web/static/src/js/views/control_panel/search/filter_menu.js
+++ b/addons/web/static/src/js/views/control_panel/search/filter_menu.js
@@ -67,14 +67,14 @@ var FilterMenu = DropdownMenu.extend({
* Add a proposition inside the custom filter edition menu.
*
* @private
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
_appendProposition: function () {
// make modern sear_filters code!!! It works but...
var prop = new search_filters.ExtendedSearchProposition(this, this.fields);
this.propositions.push(prop);
this.$('.o_apply_filter').prop('disabled', false);
- prop.insertBefore(this.$addFilterMenu);
+ return prop.insertBefore(this.$addFilterMenu);
},
/**
* Confirm a filter proposition, creates it and add it to the menu.
diff --git a/addons/web/static/src/js/views/control_panel/search/search_bar.js b/addons/web/static/src/js/views/control_panel/search/search_bar.js
index 77d6d7fa5e9..4c2e71a378f 100644
--- a/addons/web/static/src/js/views/control_panel/search/search_bar.js
+++ b/addons/web/static/src/js/views/control_panel/search/search_bar.js
@@ -46,7 +46,7 @@ var SearchBar = Widget.extend({
defs.push(self._renderFacet(facet));
});
defs.push(this._setupAutoCompletion());
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
//--------------------------------------------------------------------------
@@ -116,18 +116,21 @@ var SearchBar = Widget.extend({
* @param {Function} callback
*/
_getAutoCompleteSources: function (req, callback) {
- var defs = _.invoke(this.autoCompleteSources, 'getAutocompletionValues', req.term);
- $.when.apply($, defs).then(function () {
- callback(_(arguments).chain()
+ var defs = this.autoCompleteSources.map(function (source) {
+ return source.getAutocompletionValues(req.term);
+ });
+ Promise.all(defs).then(function (result) {
+ var resultCleaned = _(result).chain()
.compact()
.flatten(true)
- .value());
- });
+ .value();
+ callback(resultCleaned);
+ });
},
/**
* @private
* @param {Object} facet
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderFacet: function (facet) {
var searchFacet = new SearchFacet(this, facet);
@@ -136,7 +139,7 @@ var SearchBar = Widget.extend({
},
/**
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_setupAutoCompletion: function () {
var self = this;
diff --git a/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js b/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js
index f1ac0ee0f2e..8adc385a789 100644
--- a/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js
+++ b/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js
@@ -32,7 +32,7 @@ var FilterInterface = Class.extend(mixins.EventDispatcherMixin, {
* label. They will be used by search_bar in @_onAutoCompleteSelected.
*
* @param {string} value value to getAutocompletionValues
- * @returns {Deferred<null|Array>}
+ * @returns {Promise<null|Array>}
*/
getAutocompletionValues: function (value) {
var result;
@@ -46,7 +46,7 @@ var FilterInterface = Class.extend(mixins.EventDispatcherMixin, {
},
}];
}
- return $.when(result);
+ return Promise.resolve(result);
},
});
@@ -83,7 +83,7 @@ var Field = FilterInterface.extend(ServicesMixin, {
* @override
*/
getAutocompletionValues: function (value) {
- return $.when([{
+ return Promise.resolve([{
label: this._getAutocompletionLabel(value),
facet: this._getFacetValue(value),
}]);
@@ -194,7 +194,7 @@ var CharField = Field.extend({
* @override
*/
getAutocompletionValues: function (value) {
- if (_.isEmpty(value)) { return $.when(null); }
+ if (_.isEmpty(value)) { return Promise.resolve(null); }
return this._super.apply(this, arguments);
},
});
@@ -210,7 +210,7 @@ var NumberField = Field.extend({
*/
getAutocompletionValues: function (value) {
var val = this.parse(value);
- if (isNaN(val)) { return $.when(); }
+ if (isNaN(val)) { return Promise.resolve(); }
return this._super.apply(this, arguments);
},
});
@@ -261,8 +261,8 @@ var SelectionField = Field.extend({
facet: self._getFacetValue(sel)
};
}).value();
- if (_.isEmpty(results)) { return $.when(null); }
- return $.when.call(null, [{
+ if (_.isEmpty(results)) { return Promise.resolve(null); }
+ return Promise.resolve([{
label: _.escape(this.attrs.string)
}].concat(results));
},
@@ -310,15 +310,15 @@ var DateField = Field.extend({
t = (this.attrs && this.attrs.type === 'datetime') ? 'datetime' : 'date';
v = field_utils.parse[t](value, {type: t}, {timezone: true});
} catch (e) {
- return $.when(null);
+ return Promise.resolve(null);
}
var m = moment(v, t === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
- if (!m.isValid()) { return $.when(null); }
+ if (!m.isValid()) { return Promise.resolve(null); }
var dateString = field_utils.format[t](m, {type: t});
var label = this._getAutocompletionLabel(dateString);
var facet = this._getFacetValue(dateString, m.toDate());
- return $.when([{
+ return Promise.resolve([{
label: label,
facet: facet,
}]);
@@ -379,9 +379,9 @@ var ManyToOneField = CharField.extend({
* @override
*/
getAutocompletionValues: function (value) {
- if (_.isEmpty(value)) { return $.when(null); }
+ if (_.isEmpty(value)) { return Promise.resolve(null); }
var label = this._getAutocompletionLabel(value);
- return $.when([{
+ return Promise.resolve([{
label: label,
facet: this._getFacetValue(value),
expand: this._expand.bind(this),
diff --git a/addons/web/static/src/js/views/control_panel/search/search_filters.js b/addons/web/static/src/js/views/control_panel/search/search_filters.js
index 80a9019c02d..ae0ed12772e 100644
--- a/addons/web/static/src/js/views/control_panel/search/search_filters.js
+++ b/addons/web/static/src/js/views/control_panel/search/search_filters.js
@@ -40,7 +40,9 @@ var ExtendedSearchProposition = Widget.extend({
this.value = null;
},
start: function () {
- return this._super().done(this.proxy('changed'));
+ var parent = this._super();
+ parent.then(this.proxy('changed'));
+ return parent;
},
changed: function () {
var nval = this.$(".o_searchview_extended_prop_field").val();
@@ -234,10 +236,10 @@ var DateTime = Field.extend({
return str;
},
start: function () {
- return $.when(
+ return Promise.all([
this._super.apply(this, arguments),
this._create_new_widget("datewidget_0")
- );
+ ]);
},
_create_new_widget: function (name) {
this[name] = new (this._get_widget_class())(this);
diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js
index bc61e92a89a..99aabc6e66d 100644
--- a/addons/web/static/src/js/views/field_manager_mixin.js
+++ b/addons/web/static/src/js/views/field_manager_mixin.js
@@ -41,7 +41,7 @@ var FieldManagerMixin = {
* @param {string} dataPointID
* @param {Object} changes
* @param {OdooEvent} event
- * @returns {Deferred} resolves when the change has been done, and the UI
+ * @returns {Promise} resolves when the change has been done, and the UI
* updated
*/
_applyChanges: function (dataPointID, changes, event) {
@@ -66,10 +66,10 @@ var FieldManagerMixin = {
* @param {string} id basicModel Id for the changed record
* @param {string[]} fields the fields (names) that have been changed
* @param {OdooEvent} event the event that triggered the change
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmChange: function (id, fields, event) {
- return $.when();
+ return Promise.resolve();
},
/**
* This method will be called whenever a save has been triggered by a change
@@ -79,10 +79,10 @@ var FieldManagerMixin = {
* @see _onFieldChanged
* @abstract
* @param {string} id The basicModel ID for the saved record
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmSave: function (id) {
- return $.when();
+ return Promise.resolve();
},
//--------------------------------------------------------------------------
@@ -103,9 +103,9 @@ var FieldManagerMixin = {
// subrecord's form view), otherwise it bubbles up to the main form view
// but its model doesn't have any data related to the given dataPointID
event.stopPropagation();
- this._applyChanges(event.data.dataPointID, event.data.changes, event)
- .done(event.data.onSuccess || function () {})
- .fail(event.data.onFailure || function () {});
+ return this._applyChanges(event.data.dataPointID, event.data.changes, event)
+ .then(event.data.onSuccess || function () {})
+ .guardedCatch(event.data.onFailure || function () {});
},
/**
* Some widgets need to trigger a reload of their data. For example, a
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 d0400ef041f..5616a5099bc 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -84,7 +84,7 @@ var FormController = BasicController.extend({
* @todo make record creation a basic controller feature
* @param {string} [parentID] if given, the parentID will be used as parent
* for the new record.
- * @returns {Deferred}
+ * @returns {Promise}
*/
createRecord: function (parentID) {
var self = this;
@@ -165,20 +165,23 @@ var FormController = BasicController.extend({
* @override method from BasicController
* @param {jQueryElement} $node
* @param {Object} options
+ * @returns {Promise}
*/
renderPager: function ($node, options) {
options = _.extend({}, options, {
validate: this.canBeDiscarded.bind(this),
});
- this._super($node, options);
+ return this._super($node, options);
},
/**
* Instantiate and render the sidebar if a sidebar is requested
* Sets this.sidebar
* @param {jQuery} [$node] a jQuery node where the sidebar should be
* inserted
+ * @return {Promise}
**/
renderSidebar: function ($node) {
+ var self = this;
if (this.hasSidebar) {
var otherItems = [];
if (this.is_action_enabled('delete')) {
@@ -203,11 +206,12 @@ var FormController = BasicController.extend({
},
actions: _.extend(this.toolbarActions, {other: otherItems}),
});
- this.sidebar.appendTo($node);
-
- // Show or hide the sidebar according to the view mode
- this._updateSidebar();
+ return this.sidebar.appendTo($node).then(function() {
+ // Show or hide the sidebar according to the view mode
+ self._updateSidebar();
+ });
}
+ return Promise.resolve();
},
/**
* Show a warning message if the user modified a translated field. For each
@@ -296,7 +300,7 @@ var FormController = BasicController.extend({
* @private
* @override method from field manager mixin
* @param {string} id - id of the previously changed record
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmSave: function (id) {
if (id === this.handle) {
@@ -393,14 +397,19 @@ var FormController = BasicController.extend({
* @override
* @private
* @param {Object} state
- * @returns {Deferred}
+ * @returns {Promise}
*/
_update: function () {
- var title = this.getTitle();
- this._setTitle(title);
- this._updateButtons();
- this._updateSidebar();
- return this._super.apply(this, arguments).then(this.autofocus.bind(this));
+ var self = this;
+
+ return this._super.apply(this, arguments).then(function() {
+ var title = self.getTitle();
+ self._setTitle(title);
+ self._updateButtons();
+ self._updateSidebar();
+
+ self.autofocus();
+ });
},
/**
* @private
@@ -470,13 +479,11 @@ var FormController = BasicController.extend({
}
var attrs = ev.data.attrs;
if (attrs.confirm) {
- var d = $.Deferred();
- Dialog.confirm(this, attrs.confirm, {
- confirm_callback: saveAndExecuteAction,
- }).on("closed", null, function () {
- d.resolve();
+ def = new Promise(function (resolve, reject) {
+ Dialog.confirm(this, attrs.confirm, {
+ confirm_callback: saveAndExecuteAction,
+ }).on("closed", null, resolve);
});
- def = d.promise();
} else if (attrs.special === 'cancel') {
def = this._callButtonAction(attrs, ev.data.record);
} else if (!attrs.special || attrs.special === 'save') {
@@ -484,7 +491,7 @@ var FormController = BasicController.extend({
def = saveAndExecuteAction();
}
- def.always(this._enableButtons.bind(this));
+ def.then(this._enableButtons.bind(this)).guardedCatch(this._enableButtons.bind(this));
},
/**
* Called when the user wants to create a new record -> @see createRecord
@@ -659,9 +666,7 @@ var FormController = BasicController.extend({
ev.stopPropagation(); // Prevent x2m lines to be auto-saved
var self = this;
this._disableButtons();
- this.saveRecord().always(function () {
- self._enableButtons();
- });
+ this.saveRecord().then(this._enableButtons.bind(this)).guardedCatch(this._enableButtons.bind(this));
},
/**
* Called when user swipes left. Move to next record.
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index ca26d7e90ad..97ab575cbc3 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -251,7 +251,7 @@ var FormRenderer = BasicRenderer.extend({
* @param {string} [params.mode] new mode, either 'edit' or 'readonly'
* @param {string[]} [params.fieldNames] if given, the renderer will only
* update the fields in this list
- * @returns {Deferred}
+ * @returns {Promise}
*/
updateState: function (state, params) {
this.mode = (params && 'mode' in params) ? params.mode : this.mode;
@@ -342,8 +342,8 @@ var FormRenderer = BasicRenderer.extend({
* @private
*/
_postProcessField: function (widget, node) {
+ this._super.apply(this, arguments);
this._setIDForLabel(widget, this._getIDForLabel(node.attrs.name));
- this._handleAttributes(widget.$el, node);
if (JSON.parse(node.attrs.default_focus || "0")) {
this.defaultFocusField = widget;
}
@@ -355,7 +355,14 @@ var FormRenderer = BasicRenderer.extend({
*/
_renderButtonBox: function (node) {
var self = this;
- var $result = $('<' + node.tag + '>', { 'class': 'o_not_full' });
+ var $result = $('<' + node.tag + '>', {class: 'o_not_full'});
+
+ // The rendering of buttons may be async (see renderFieldWidget), so we
+ // must wait for the buttons to be ready (and their modifiers to be
+ // applied) before manipulating them, as we check if they are visible or
+ // not. To do so, we extract from this.defs the promises corresponding
+ // to the buttonbox buttons, and wait for them to be resolved.
+ var nextDefIndex = this.defs.length;
var buttons = _.map(node.children, function (child) {
if (child.tag === 'button') {
return self._renderStatButton(child);
@@ -363,48 +370,62 @@ var FormRenderer = BasicRenderer.extend({
return self._renderNode(child);
}
});
- var buttons_partition = _.partition(buttons, function ($button) {
- return $button.is('.o_invisible_modifier');
+
+ // At this point, each button is an empty div that will be replaced by
+ // the real $el of the button when it is ready (with replaceWith).
+ // However, this only works if the empty div is appended somewhere, so
+ // we here append them into a wrapper, and unwrap them once they have
+ // been replaced.
+ var $tempWrapper = $('<div>');
+ _.each(buttons, function ($button) {
+ $button.appendTo($tempWrapper);
});
- var invisible_buttons = buttons_partition[0];
- var visible_buttons = buttons_partition[1];
+ var defs = this.defs.slice(nextDefIndex);
+ Promise.all(defs).then(function () {
+ buttons = $tempWrapper.children();
+ var buttons_partition = _.partition(buttons, function (button) {
+ return $(button).is('.o_invisible_modifier');
+ });
+ var invisible_buttons = buttons_partition[0];
+ var visible_buttons = buttons_partition[1];
- // Get the unfolded buttons according to window size
- var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7;
- var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons);
+ // Get the unfolded buttons according to window size
+ var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7;
+ var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons);
- // Get the folded buttons
- var folded_buttons = visible_buttons.slice(nb_buttons);
- if (folded_buttons.length === 1) {
- unfolded_buttons = buttons;
- folded_buttons = [];
- }
+ // Get the folded buttons
+ var folded_buttons = visible_buttons.slice(nb_buttons);
+ if (folded_buttons.length === 1) {
+ unfolded_buttons = buttons;
+ folded_buttons = [];
+ }
- // Toggle class to tell if the button box is full (CSS requirement)
- var full = (visible_buttons.length > nb_buttons);
- $result.toggleClass('o_full', full).toggleClass('o_not_full', !full);
+ // Toggle class to tell if the button box is full (CSS requirement)
+ var full = (visible_buttons.length > nb_buttons);
+ $result.toggleClass('o_full', full).toggleClass('o_not_full', !full);
- // Add the unfolded buttons
- _.each(unfolded_buttons, function ($button) {
- $button.appendTo($result);
- });
+ // Add the unfolded buttons
+ _.each(unfolded_buttons, function (button) {
+ $(button).appendTo($result);
+ });
- // Add the dropdown with folded buttons if any
- if (folded_buttons.length) {
- $result.append(dom.renderButton({
- attrs: {
- class: 'oe_stat_button o_button_more dropdown-toggle',
- 'data-toggle': 'dropdown',
- },
- text: _t("More"),
- }));
+ // Add the dropdown with folded buttons if any
+ if (folded_buttons.length) {
+ $result.append(dom.renderButton({
+ attrs: {
+ 'class': 'oe_stat_button o_button_more dropdown-toggle',
+ 'data-toggle': 'dropdown',
+ },
+ text: _t("More"),
+ }));
- var $dropdown = $("<div>", {'class': "dropdown-menu o_dropdown_more", role: "menu"});
- _.each(folded_buttons, function ($button) {
- $button.addClass('dropdown-item').appendTo($dropdown);
- });
- $dropdown.appendTo($result);
- }
+ var $dropdown = $("<div>", {class: "dropdown-menu o_dropdown_more", role: "menu"});
+ _.each(folded_buttons, function (button) {
+ $(button).addClass('dropdown-item').appendTo($dropdown);
+ });
+ $dropdown.appendTo($result);
+ }
+ });
this._handleAttributes($result, node);
this._registerModifiers(node, this.state, $result);
@@ -477,6 +498,7 @@ var FormRenderer = BasicRenderer.extend({
_renderInnerGroup: function (node) {
var self = this;
var $result = $('<table/>', {class: 'o_group o_inner_group'});
+ var $tbody = $('<tbody />').appendTo($result);
this._handleAttributes($result, node);
this._registerModifiers(node, this.state, $result);
@@ -490,7 +512,7 @@ var FormRenderer = BasicRenderer.extend({
var rows = [];
var $currentRow = $('<tr/>');
var currentColspan = 0;
- _.each(node.children, function (child) {
+ node.children.forEach(function (child) {
if (child.tag === 'newline') {
rows.push($currentRow);
$currentRow = $('<tr/>');
@@ -537,7 +559,7 @@ var FormRenderer = BasicRenderer.extend({
var $el = $(el);
$el.css('width', ((parseInt($el.attr('colspan'), 10) || 1) * nonLabelColSize) + '%');
});
- $result.append($tr);
+ $tbody.append($tr);
});
return $result;
@@ -575,7 +597,7 @@ var FormRenderer = BasicRenderer.extend({
*
* For fields, it will return the $el of the field widget. Note that this
* method is synchronous, field widgets are instantiated and appended, but
- * if they are asynchronous, they register their deferred in this.defs, and
+ * if they are asynchronous, they register their promises in this.defs, and
* the _renderView method will properly wait.
*
* @private
@@ -705,7 +727,8 @@ var FormRenderer = BasicRenderer.extend({
if (node.attrs.class) {
$result.addClass(node.attrs.class);
}
- $result.append(_.map(node.children, this._renderNode.bind(this)));
+ var allNodes = node.children.map(this._renderNode.bind(this));
+ $result.append(allNodes);
return $result;
},
/**
@@ -870,7 +893,7 @@ var FormRenderer = BasicRenderer.extend({
_renderTagSheet: function (node) {
this.has_sheet = true;
var $sheet = $('<div>', {class: 'clearfix o_form_sheet'});
- $sheet.append(_.map(node.children, this._renderNode.bind(this)));
+ $sheet.append(node.children.map(this._renderNode.bind(this)));
return $sheet;
},
/**
@@ -885,12 +908,12 @@ var FormRenderer = BasicRenderer.extend({
},
/**
* Main entry point for the rendering. From here, we call _renderNode on
- * the root of the arch, then, when every deferred (from the field widgets)
+ * the root of the arch, then, when every promise (from the field widgets)
* are done, it will resolves itself.
*
* @private
* @override method from BasicRenderer
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderView: function () {
var self = this;
@@ -901,17 +924,17 @@ var FormRenderer = BasicRenderer.extend({
var $form = this._renderNode(this.arch).addClass(this.className);
delete this.defs;
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
self._updateView($form.contents());
if (self.state.res_id in self.alertFields) {
self.displayTranslationAlert();
}
- }, function () {
- $form.remove();
}).then(function(){
if (self.lastActivatedFieldIndex >= 0) {
self._activateNextFieldWidget(self.state, self.lastActivatedFieldIndex);
}
+ }).guardedCatch(function () {
+ $form.remove();
});
},
/**
diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js
index 5a00350c68f..0703d9713d5 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -93,7 +93,7 @@ var FormView = BasicView.extend({
*
* @private
* @param {Widget} parent the parent of the model, if it has to be created
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadSubviews: function (parent) {
var self = this;
@@ -145,7 +145,7 @@ var FormView = BasicView.extend({
}
});
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* We set here the limit for the number of records fetched (in one page).
diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js
index 4c359a4778f..91b017524d8 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -50,7 +50,7 @@ return AbstractModel.extend({
* @param {string[]} params.domain
* @param {Object} params.intervalMapping object linking fieldNames with intervals.
* this could be useful to simplify the code. For now this parameter is not used.
- * @returns {Deferred} The deferred does not return a handle, we don't need
+ * @returns {Promise} The promise does not return a handle, we don't need
* to keep track of various entities.
*/
load: function (params) {
@@ -92,7 +92,7 @@ return AbstractModel.extend({
* @param {string[]} [params.groupBy]
* @param {string} [params.mode] one of 'bar', 'pie', 'line'
* @param {string} [params.measure] a valid field name
- * @returns {Deferred}
+ * @returns {Promise}
*/
reload: function (handle, params) {
if ('context' in params) {
@@ -130,7 +130,7 @@ return AbstractModel.extend({
}
if ('mode' in params) {
this.chart.mode = params.mode;
- return $.when();
+ return Promise.resolve();
}
return this._loadGraph();
},
@@ -145,7 +145,7 @@ return AbstractModel.extend({
* in the field list, because they can be defined with an aggregation
* function, such as my_date:week
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadGraph: function () {
var groupedBy = this.chart.groupedBy;
@@ -186,7 +186,7 @@ return AbstractModel.extend({
}).then(this._processData.bind(this, 'comparisonData')));
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Since read_group is insane and returns its result on different keys
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index 9353301ca09..6311df5d8b2 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -99,7 +99,7 @@ return AbstractRenderer.extend({
*
* @override
* @private
- * @returns {Deferred} The _super deferred is actually resolved immediately
+ * @returns {Promise} The _super promise is actually resolved immediately
*/
_render: function () {
if (this.to_remove) {
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 2828ca491ca..b4aecd7a236 100644
--- a/addons/web/static/src/js/views/kanban/kanban_column.js
+++ b/addons/web/static/src/js/views/kanban/kanban_column.js
@@ -97,10 +97,7 @@ var KanbanColumn = Widget.extend({
this.$header = this.$('.o_kanban_header');
for (var i = 0; i < this.data_records.length; i++) {
- var def = this._addRecord(this.data_records[i]);
- if (def.state() === 'pending') {
- defs.push(def);
- }
+ defs.push(this._addRecord(this.data_records[i]));
}
if (!config.device.isMobile) {
@@ -170,7 +167,7 @@ var KanbanColumn = Widget.extend({
this.$('.o_kanban_load_more').html(QWeb.render('KanbanView.LoadMore', {widget: this}));
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Called when a record has been quick created, as a new column is rendered
@@ -193,7 +190,7 @@ var KanbanColumn = Widget.extend({
/**
* Adds the quick create record to the top of the column.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
addQuickCreate: function () {
if (this.folded) {
@@ -205,7 +202,7 @@ var KanbanColumn = Widget.extend({
}
if (this.quickCreateWidget) {
- return $.Deferred().reject();
+ return Promise.reject();
}
this.trigger_up('close_quick_create'); // close other quick create widgets
this.trigger_up('start_quick_create');
@@ -245,7 +242,7 @@ var KanbanColumn = Widget.extend({
* @param {Object} [options]
* @param {string} [options.position]
* 'before' to add at the top, add at the bottom by default
- * @return {Deferred}
+ * @return {Promise}
*/
_addRecord: function (recordState, options) {
var record = new this.KanbanRecord(this, recordState, this.record_options);
diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js
index 3b41b26e015..58174c7812d 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -76,6 +76,7 @@ var KanbanController = BasicController.extend({
/**
* @param {jQueryElement} $node
+ * @returns {Promise}
*/
renderButtons: function ($node) {
if (this.hasButtons && this.is_action_enabled('create')) {
@@ -86,8 +87,9 @@ var KanbanController = BasicController.extend({
this.$buttons.on('click', 'button.o-kanban-button-new', this._onButtonNew.bind(this));
this.$buttons.on('keydown', this._onButtonsKeyDown.bind(this));
this._updateButtons();
- this.$buttons.appendTo($node);
+ return Promise.resolve(this.$buttons.appendTo($node));
}
+ return Promise.resolve();
},
/**
* Override to add the domain coming from the searchPanel (if any) to the
@@ -110,7 +112,7 @@ var KanbanController = BasicController.extend({
params.domain = this.controlPanelDomain.concat(this.searchPanelDomain);
var superProm = this._super.apply(this, arguments);
var searchPanelProm = this._updateSearchPanel();
- return $.when(superProm, searchPanelProm).then(function () {
+ return Promise.all([superProm, searchPanelProm]).then(function () {
// searchPanel has been re-rendered, so re-render the view
return self.renderer.render();
});
@@ -124,7 +126,7 @@ var KanbanController = BasicController.extend({
* @override method comes from field manager mixin
* @private
* @param {string} id local id from the basic record data
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmSave: function (id) {
var data = this.model.get(this.handle, {raw: true});
@@ -197,7 +199,7 @@ var KanbanController = BasicController.extend({
/**
* @param {number[]} ids
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_resequenceColumns: function (ids) {
var state = this.model.get(this.handle, {raw: true});
@@ -212,7 +214,7 @@ var KanbanController = BasicController.extend({
* @private
* @param {string} column_id
* @param {string[]} ids
- * @returns {Deferred}
+ * @returns {Promise}
*/
_resequenceRecords: function (column_id, ids) {
var self = this;
@@ -246,7 +248,7 @@ var KanbanController = BasicController.extend({
},
/**
* @private
- * @returns {$.Promise}
+ * @returns {Promise}
*/
_updateSearchPanel: function () {
return this._searchPanel.update({searchDomain: this.controlPanelDomain});
@@ -296,7 +298,7 @@ var KanbanController = BasicController.extend({
self.renderer.updateColumn(db_id, data);
});
});
- }).fail(this.reload.bind(this, {}));
+ }).guardedCatch(this.reload.bind(this));
},
/**
* @private
@@ -400,7 +402,7 @@ var KanbanController = BasicController.extend({
var relatedModelName = state.fields[state.groupedBy[0]].relation;
this.model
.deleteRecords([column.db_id], relatedModelName)
- .done(function () {
+ .then(function () {
self.update({}, {reload: !column.isEmpty()});
});
},
@@ -415,11 +417,12 @@ var KanbanController = BasicController.extend({
var self = this;
this.model.loadColumnRecords(ev.data.columnID).then(function (dbID) {
var data = self.model.get(dbID);
- self.renderer.updateColumn(dbID, data);
- self._updateEnv();
- if (ev.data.onSuccess) {
- ev.data.onSuccess();
- }
+ return self.renderer.updateColumn(dbID, data).then(function() {
+ self._updateEnv();
+ if (ev.data.onSuccess) {
+ ev.data.onSuccess();
+ }
+ });
});
},
/**
@@ -470,8 +473,8 @@ var KanbanController = BasicController.extend({
this.model.createRecordInGroup(column.db_id, values)
.then(update)
- .fail(function (error, ev) {
- ev.preventDefault();
+ .guardedCatch(function (reason) {
+ reason.event.preventDefault();
var columnState = self.model.get(column.db_id, {raw: true});
var context = columnState.getContext();
var state = self.model.get(self.handle, {raw: true});
@@ -538,11 +541,15 @@ var KanbanController = BasicController.extend({
*
* @private
* @param {OdooEvent} ev
+ * @param {function} [ev.data.onSuccess] callback to execute after applying
+ * changes
*/
_onUpdateRecord: function (ev) {
+ var onSuccess = ev.data.onSuccess;
+ delete ev.data.onSuccess;
var changes = _.clone(ev.data);
ev.data.force_save = true;
- this._applyChanges(ev.target.db_id, changes, ev);
+ this._applyChanges(ev.target.db_id, changes, ev).then(onSuccess);
},
/**
* Allow the user to archive/restore all the records of a column.
diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js
index bfb6f5e0cc9..8b1135d3f15 100644
--- a/addons/web/static/src/js/views/kanban/kanban_model.js
+++ b/addons/web/static/src/js/views/kanban/kanban_model.js
@@ -20,7 +20,7 @@ var KanbanModel = BasicModel.extend({
*
* @param {string} groupID localID of the group
* @param {integer} resId id of the record
- * @returns {Deferred<string>} resolves to the local id of the new record
+ * @returns {Promise<string>} resolves to the local id of the new record
*/
addRecordToGroup: function (groupID, resId) {
var self = this;
@@ -52,7 +52,7 @@ var KanbanModel = BasicModel.extend({
*
* @param {string} name
* @param {string} parentID localID of the parent of the group
- * @returns {Deferred<string>} resolves to the local id of the new group
+ * @returns {Promise<string>} resolves to the local id of the new group
*/
createGroup: function (name, parentID) {
var self = this;
@@ -60,7 +60,7 @@ var KanbanModel = BasicModel.extend({
var groupBy = parent.groupedBy[0];
var groupByField = parent.fields[groupBy];
if (!groupByField || groupByField.type !== 'many2one') {
- return $.Deferred().reject(); // only supported when grouped on m2o
+ return Promise.reject(); // only supported when grouped on m2o
}
return this._rpc({
model: groupByField.relation,
@@ -99,7 +99,7 @@ var KanbanModel = BasicModel.extend({
*
* @param {string} groupID
* @param {Object} values
- * @returns {Deferred} resolved with the local id of the created record
+ * @returns {Promise} resolved with the local id of the created record
*/
createRecordInGroup: function (groupID, values) {
var self = this;
@@ -187,7 +187,7 @@ var KanbanModel = BasicModel.extend({
* Opens a given group and loads its <limit> first records
*
* @param {string} groupID
- * @returns {Deferred}
+ * @returns {Promise}
*/
loadColumnRecords: function (groupID) {
var dataPoint = this.localData[groupID];
@@ -198,7 +198,7 @@ var KanbanModel = BasicModel.extend({
* Load more records in a group.
*
* @param {string} groupID localID of the group
- * @returns {Deferred<string>} resolves to the localID of the group
+ * @returns {Promise<string>} resolves to the localID of the group
*/
loadMore: function (groupID) {
var group = this.localData[groupID];
@@ -213,7 +213,7 @@ var KanbanModel = BasicModel.extend({
* @param {string} recordID localID of the record
* @param {string} groupID localID of the new group of the record
* @param {string} parentID localID of the parent
- * @returns {Deferred<string[]>} resolves to a pair [oldGroupID, newGroupID]
+ * @returns {Promise<string[]>} resolves to a pair [oldGroupID, newGroupID]
*/
moveRecord: function (recordID, groupID, parentID) {
var self = this;
@@ -330,7 +330,7 @@ var KanbanModel = BasicModel.extend({
/**
* @private
* @param {Object} dataPoint
- * @returns {Deferred<Object>}
+ * @returns {Promise<Object>}
*/
_readProgressBarGroup: function (list, options) {
var self = this;
@@ -345,7 +345,8 @@ var KanbanModel = BasicModel.extend({
context: list.context,
},
});
- return $.when(groupsDef, progressBarDef).then(function (a, data) {
+ return Promise.all([groupsDef, progressBarDef]).then(function (results) {
+ var data = results[1];
_.each(list.data, function (groupID) {
var group = self.localData[groupID];
group.progressBarValues = _.extend({
@@ -365,13 +366,13 @@ var KanbanModel = BasicModel.extend({
*
* @private
* @param {Object} list a list of groups
- * @returns {Deferred}
+ * @returns {Promise}
*/
_readTooltipFields: function (list) {
var self = this;
var groupedByField = list.fields[list.groupedBy[0].split(':')[0]];
if (groupedByField.type !== 'many2one') {
- return $.when();
+ return Promise.resolve();
}
var groupIds = _.reduce(list.data, function (groupIds, id) {
var res_id = self.get(id, {raw: true}).res_id;
@@ -400,16 +401,16 @@ var KanbanModel = BasicModel.extend({
});
});
}
- return $.when();
+ return Promise.resolve();
},
/**
- * Reloads all progressbar data. This is done after given deferred and
- * insures that the given deferred's result is not lost.
+ * Reloads all progressbar data. This is done after given promise and
+ * insures that the given promise's result is not lost.
*
* @private
* @param {string} recordID
- * @param {Deferred} def
- * @returns {Deferred}
+ * @param {Promise} def
+ * @returns {Promise}
*/
_reloadProgressBarGroupFromRecord: function (recordID, def) {
var element = this.localData[recordID];
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index 3dc1d6788bf..116de142b0c 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -62,7 +62,7 @@ var KanbanRecord = Widget.extend({
* @override
*/
start: function () {
- return $.when(this._super.apply(this, arguments), this._render());
+ return Promise.all([this._super.apply(this, arguments), this._render()]);
},
/**
* Called each time the record is attached to the DOM.
@@ -85,7 +85,7 @@ var KanbanRecord = Widget.extend({
* Re-renders the record with a new state
*
* @param {Object} state
- * @returns {Deferred}
+ * @returns {Promise}
*/
update: function (state) {
// detach the widgets because the record will empty its $el, which will
@@ -147,7 +147,7 @@ var KanbanRecord = Widget.extend({
}
if (typeof variable === 'string') {
var index = 0;
- for (var i = 0 ; i < variable.length ; i++) {
+ for (var i = 0; i < variable.length; i++) {
index += variable.charCodeAt(i);
}
return index % NB_KANBAN_RECORD_COLORS;
@@ -195,7 +195,7 @@ var KanbanRecord = Widget.extend({
field = options.preview_image;
var unique = this.record.__last_update && this.record.__last_update.value.replace(/[^0-9]/g, '');
var session = this.getSession();
- url = session.url('/web/image', {model: model, field: field, id: id, unique: unique});
+ url = session.url('/web/image', { model: model, field: field, id: id, unique: unique });
if (cache !== undefined) {
// Set the cache duration in seconds.
url += '&cache=' + parseInt(cache, 10);
@@ -300,6 +300,7 @@ var KanbanRecord = Widget.extend({
// field's widgets point of view
// that dict being shared between records, we don't modify it
// in place
+ var self = this;
var attrs = Object.create(null);
_.each(this.fieldsInfo[field_name], function (value, key) {
if (_.str.startsWith(key, 't-att-')) {
@@ -308,13 +309,13 @@ var KanbanRecord = Widget.extend({
}
attrs[key] = value;
});
- var options = _.extend({}, this.options, {attrs: attrs});
+ var options = _.extend({}, this.options, { attrs: attrs });
var widget = new Widget(this, field_name, this.state, options);
var def = widget.replace($field);
- if (def.state() === 'pending') {
- this.defs.push(def);
- }
- this._setFieldDisplay(widget.$el, field_name);
+ this.defs.push(def);
+ def.then(function () {
+ self._setFieldDisplay(widget.$el, field_name);
+ });
return widget;
},
_processWidgets: function () {
@@ -324,23 +325,23 @@ var KanbanRecord = Widget.extend({
var Widget = widgetRegistry.get($field.attr('name'));
var widget = new Widget(self, self.state);
- var def = widget._widgetRenderAndInsert(function () {});
- if (def.state() === 'pending') {
- self.defs.push(def);
- }
- widget.$el.addClass('o_widget');
- $field.replaceWith(widget.$el);
+ var def = widget._widgetRenderAndInsert(function () { });
+ self.defs.push(def);
+ def.then(function () {
+ $field.replaceWith(widget.$el);
+ widget.$el.addClass('o_widget');
+ });
});
},
/**
* Renders the record
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
this.defs = [];
this._replaceElement(this.qweb.render('kanban-box', this.qweb_context));
- this.$el.addClass('o_kanban_record').attr("tabindex",0);
+ this.$el.addClass('o_kanban_record').attr("tabindex", 0);
this.$el.attr('role', 'article');
this.$el.data('record', this);
if (this.$el.hasClass('oe_kanban_global_click') ||
@@ -357,9 +358,9 @@ var KanbanRecord = Widget.extend({
this._attachTooltip();
// We use boostrap tooltips for better and faster display
- this.$('span.o_tag').tooltip({delay: {'show': 50}});
+ this.$('span.o_tag').tooltip({ delay: { 'show': 50 } });
- return $.when.apply(this, this.defs);
+ return Promise.all(this.defs);
},
/**
* Sets particular classnames on a field $el according to the
@@ -419,7 +420,7 @@ var KanbanRecord = Widget.extend({
var colorHelp = _.str.sprintf(_t("Card color: %s"), this._getColorname(this.recordData[color_field]));
var colorClass = this._getColorClassname(this.recordData[color_field]);
this.$el.addClass(colorClass);
- this.$el.prepend('<span title="' + colorHelp + '" aria-label="' + colorHelp +'" role="img" class="oe_kanban_color_help"/>');
+ this.$el.prepend('<span title="' + colorHelp + '" aria-label="' + colorHelp + '" role="img" class="oe_kanban_color_help"/>');
}
},
/**
@@ -454,7 +455,7 @@ var KanbanRecord = Widget.extend({
r.raw_value = value.toDate();
} else if (r.type === 'one2many' || r.type === 'many2many') {
r.raw_value = value.count ? value.res_ids : [];
- } else if (r.type === 'many2one' ) {
+ } else if (r.type === 'many2one') {
r.raw_value = value && value.res_id || false;
} else {
r.raw_value = value;
@@ -554,13 +555,13 @@ var KanbanRecord = Widget.extend({
switch (type) {
case 'edit':
- this.trigger_up('open_record', {id: this.db_id, mode: 'edit'});
+ this.trigger_up('open_record', { id: this.db_id, mode: 'edit' });
break;
case 'open':
- this.trigger_up('open_record', {id: this.db_id});
+ this.trigger_up('open_record', { id: this.db_id });
break;
case 'delete':
- this.trigger_up('kanban_record_delete', {id: this.db_id, record: this});
+ this.trigger_up('kanban_record_delete', { id: this.db_id, record: this });
break;
case 'action':
case 'object':
diff --git a/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js b/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js
index 741cc6a898a..e69914acd49 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js
@@ -47,14 +47,14 @@ var RecordQuickCreate = Widget.extend({
*/
willStart: function () {
var self = this;
- var def1 = this._super.apply(this, arguments);
- var def2;
+ var superWillStart = this._super.apply(this, arguments);
+ var viewsLoaded;
if (this.formViewRef) {
var views = [[false, 'form']];
var context = _.extend({}, this.context, {
form_view_ref: this.formViewRef,
});
- def2 = this.loadViews(this.model, context, views);
+ viewsLoaded = this.loadViews(this.model, context, views);
} else {
var fieldsView = {};
fieldsView.arch = '<form>' +
@@ -65,9 +65,9 @@ var RecordQuickCreate = Widget.extend({
};
fieldsView.fields = fields;
fieldsView.viewFields = fields;
- def2 = $.when({form: fieldsView});
+ viewsLoaded = Promise.resolve({form: fieldsView});
}
- def2 = def2.then(function (fieldsViews) {
+ viewsLoaded = viewsLoaded.then(function (fieldsViews) {
var formView = new QuickCreateFormView(fieldsViews.form, {
context: self.context,
modelName: self.model,
@@ -78,7 +78,7 @@ var RecordQuickCreate = Widget.extend({
return self.controller.appendTo(document.createDocumentFragment());
});
});
- return $.when(def1, def2);
+ return Promise.all([superWillStart, viewsLoaded]);
},
/**
* @override
@@ -113,7 +113,7 @@ var RecordQuickCreate = Widget.extend({
* have been made yet
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
cancel: function () {
var self = this;
@@ -129,7 +129,6 @@ var RecordQuickCreate = Widget.extend({
//--------------------------------------------------------------------------
/**
- * @override
* @private
* @param {Object} [options]
* @param {boolean} [options.openRecord] set to true to directly open the
@@ -139,7 +138,7 @@ var RecordQuickCreate = Widget.extend({
var self = this;
if (this._disabled) {
// don't do anything if we are already creating a record
- return $.Deferred();
+ return;
}
// disable the widget to prevent the user from creating multiple records
// with the current values ; if the create works, the widget will be
@@ -157,13 +156,13 @@ var RecordQuickCreate = Widget.extend({
} else {
self._enableQuickCreate();
}
- }).fail(this._enableQuickCreate.bind(this));
+ }).guardedCatch(this._enableQuickCreate.bind(this));
},
/**
* Notifies the environment that the quick creation must be cancelled
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_cancel: function () {
this.trigger_up('cancel_quick_create');
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js
index 252b4587af6..eda15f706d3 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -148,7 +148,7 @@ var KanbanRenderer = BasicRenderer.extend({
/**
* Displays the quick create record in the first column.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
addQuickCreate: function () {
return this.widgets[0].addQuickCreate();
@@ -187,7 +187,7 @@ var KanbanRenderer = BasicRenderer.extend({
* @param {boolean} [options.openQuickCreate] if true, directly opens the
* QuickCreate widget in the updated column
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
updateColumn: function (localID, columnState, options) {
var self = this;
@@ -204,7 +204,7 @@ var KanbanRenderer = BasicRenderer.extend({
if (options && options.openQuickCreate) {
def = newColumn.addQuickCreate();
}
- return $.when(def).then(function () {
+ return Promise.resolve(def).then(function () {
newColumn.$el.insertAfter(column.$el);
self._toggleNoContentHelper();
// When a record has been quick created, the new column directly
@@ -221,7 +221,7 @@ var KanbanRenderer = BasicRenderer.extend({
* Updates a given record with its new state.
*
* @param {Object} recordState
- * @returns {Deferred}
+ * @returns {Promise}
*/
updateRecord: function (recordState) {
var isGrouped = !!this.state.groupedBy.length;
@@ -242,7 +242,7 @@ var KanbanRenderer = BasicRenderer.extend({
if (record) {
return record.update(recordState);
}
- return $.when();
+ return Promise.resolve();
},
/**
* @override
@@ -295,10 +295,13 @@ var KanbanRenderer = BasicRenderer.extend({
* @param {integer} nbDivs the number of divs to append
*/
_renderGhostDivs: function (fragment, nbDivs) {
+ var ghostDefs = [];
for (var $ghost, i = 0; i < nbDivs; i++) {
$ghost = $('<div>').addClass('o_kanban_record o_kanban_ghost');
- $ghost.appendTo(fragment);
+ var def = $ghost.appendTo(fragment);
+ ghostDefs.push(def);
}
+ return Promise.all(ghostDefs);
},
/**
* Renders an grouped kanban view in a fragment.
@@ -321,10 +324,7 @@ var KanbanRenderer = BasicRenderer.extend({
def = column.appendTo(fragment);
self.widgets.push(column);
}
- if (def.state() === 'pending') {
- self.defs.push(def);
- }
-
+ self.defs.push(def);
});
// remove previous sorting
@@ -359,13 +359,13 @@ var KanbanRenderer = BasicRenderer.extend({
this.quickCreate = new ColumnQuickCreate(this, {
examples: this.examples,
});
- this.quickCreate.appendTo(fragment).then(function () {
+ this.defs.push(this.quickCreate.appendTo(fragment).then(function () {
// Open it directly if there is no column yet
if (!self.state.data.length) {
self.quickCreate.toggleFold();
self._renderExampleBackground(fragment);
}
- });
+ }));
}
}
},
@@ -382,13 +382,14 @@ var KanbanRenderer = BasicRenderer.extend({
var kanbanRecord = new KanbanRecord(self, record, self.recordOptions);
self.widgets.push(kanbanRecord);
var def = kanbanRecord.appendTo(fragment);
- if (def.state() === 'pending') {
- self.defs.push(def);
- }
+ self.defs.push(def);
});
// append ghost divs to ensure that all kanban records are left aligned
- this._renderGhostDivs(fragment, 6);
+ var prom = Promise.all(self.defs).then(function () {
+ return self._renderGhostDivs(fragment, 6);
+ });
+ this.defs.push(prom);
},
/**
* @override
@@ -398,28 +399,31 @@ var KanbanRenderer = BasicRenderer.extend({
var self = this;
var oldWidgets = this.widgets;
this.widgets = [];
- this.$el.empty();
+ // render the kanban view
var isGrouped = !!this.state.groupedBy.length;
- this.$el.toggleClass('o_kanban_grouped', isGrouped);
- this.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
var fragment = document.createDocumentFragment();
- // render the kanban view
- this.defs = [];
+ var defs = [];
+ this.defs = defs;
if (isGrouped) {
this._renderGrouped(fragment);
} else {
this._renderUngrouped(fragment);
}
- this.$el.append(fragment);
- this._toggleNoContentHelper();
- var defs = this.defs;
+ delete this.defs;
+
return this._super.apply(this, arguments).then(function () {
- _.invoke(oldWidgets, 'destroy');
- if (self._isInDom) {
- _.invoke(self.widgets, 'on_attach_callback');
- }
- return $.when.apply(null, defs);
+ return Promise.all(defs).then(function () {
+ self.$el.empty();
+ self.$el.toggleClass('o_kanban_grouped', isGrouped);
+ self.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
+ self.$el.append(fragment);
+ self._toggleNoContentHelper();
+ _.invoke(oldWidgets, 'destroy');
+ if (self._isInDom) {
+ _.invoke(self.widgets, 'on_attach_callback');
+ }
+ });
});
},
/**
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
index a9576e9836f..eae16968833 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
@@ -75,7 +75,7 @@ KanbanRenderer.include({
/**
* Displays the quick create record in the active column
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
addQuickCreate: function () {
return this.widgets[this.activeColumnIndex].addQuickCreate();
@@ -260,26 +260,26 @@ KanbanRenderer.include({
* @private
* @param {integer} moveToIndex index of the column to move to
* @param {boolean} [animate=false] set to true to animate
- * @returns {Deferred} resolved when the new current group has been loaded
+ * @returns {Promise} resolved when the new current group has been loaded
* and displayed
*/
_moveToGroup: function (moveToIndex, animate) {
var self = this;
if (moveToIndex < 0 || moveToIndex >= this.widgets.length) {
this._layoutUpdate(animate);
- return $.when();
+ return Promise.resolve();
}
- var def = $.Deferred();
this.activeColumnIndex = moveToIndex;
var column = this.widgets[this.activeColumnIndex];
- this.trigger_up('kanban_load_records', {
- columnID: column.db_id,
- onSuccess: function () {
- self._layoutUpdate(animate);
- def.resolve();
- },
+ return new Promise(function (resolve) {
+ self.trigger_up('kanban_load_records', {
+ columnID: column.db_id,
+ onSuccess: function () {
+ self._layoutUpdate(animate);
+ resolve();
+ },
+ });
});
- return def;
},
/**
@@ -287,22 +287,23 @@ KanbanRenderer.include({
* @private
*/
_renderGrouped: function (fragment) {
- var result = this._super.apply(this, arguments);
- var data = [];
- _.each(this.state.data, function (group) {
- if (!group.value) {
- group = _.extend({}, group, {value: _t('Undefined')});
- data.unshift(group);
- }
- else {
- data.push(group);
- }
- });
+ var self = this;
+ this._super.apply(this, arguments);
+ this.defs.push(Promise.all(this.defs).then(function () {
+ var data = [];
+ _.each(self.state.data, function (group) {
+ if (!group.value) {
+ group = _.extend({}, group, {value: _t('Undefined')});
+ data.unshift(group);
+ } else {
+ data.push(group);
+ }
+ });
- $(qweb.render('KanbanView.MobileTabs', {
- data: data,
- })).prependTo(fragment);
- return result;
+ $(qweb.render('KanbanView.MobileTabs', {
+ data: data,
+ })).prependTo(fragment);
+ }));
},
/**
diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js
index 37f7fdd42ef..49ba9b74034 100644
--- a/addons/web/static/src/js/views/kanban/kanban_view.js
+++ b/addons/web/static/src/js/views/kanban/kanban_view.js
@@ -139,7 +139,7 @@ var KanbanView = BasicView.extend({
/**
* @private
* @param {Widget} parent
- * @returns {$.Promise} resolved when the searchPanel is ready
+ * @returns {Promise} resolved when the searchPanel is ready
*/
_createSearchPanel: function (parent) {
var self = this;
diff --git a/addons/web/static/src/js/views/kanban/quick_create_form_view.js b/addons/web/static/src/js/views/kanban/quick_create_form_view.js
index af769d62095..8725c3104de 100644
--- a/addons/web/static/src/js/views/kanban/quick_create_form_view.js
+++ b/addons/web/static/src/js/views/kanban/quick_create_form_view.js
@@ -71,7 +71,7 @@ var QuickCreateFormController = FormController.extend({
*/
commitChanges: function () {
var mutexDef = this.mutex.getUnlockedDef();
- return $.when(mutexDef, this.renderer.commitChanges(this.handle));
+ return Promise.all([mutexDef, this.renderer.commitChanges(this.handle)]);
},
/**
* @returns {Object} the changes done on the current record
diff --git a/addons/web/static/src/js/views/kanban/search_panel.js b/addons/web/static/src/js/views/kanban/search_panel.js
index b906d02b89a..d06eacbdb07 100644
--- a/addons/web/static/src/js/views/kanban/search_panel.js
+++ b/addons/web/static/src/js/views/kanban/search_panel.js
@@ -55,7 +55,7 @@ var SearchPanel = Widget.extend({
var loadProm = this._fetchCategories().then(function () {
return self._fetchFilters().then(self._applyDefaultFilterValues.bind(self));
});
- return $.when(loadProm, this._super.apply(this, arguments));
+ return Promise.all([loadProm, this._super.apply(this, arguments)]);
},
/**
* @override
@@ -82,18 +82,18 @@ var SearchPanel = Widget.extend({
*
* @param {Object} params
* @param {Array[]} params.searchDomain domain coming from controlPanel
- * @returns {$.Promise}
+ * @returns {Promise}
*/
update: function (params) {
var currentSearchDomainStr = JSON.stringify(this.searchDomain);
var newSearchDomainStr = JSON.stringify(params.searchDomain);
- var def;
+ var filtersProm;
if (this.needReload || (currentSearchDomainStr !== newSearchDomainStr)) {
this.needReload = false;
this.searchDomain = params.searchDomain;
- def = this._fetchFilters();
+ filtersProm = this._fetchFilters();
}
- return $.when(def).then(this._render.bind(this));
+ return Promise.resolve(filtersProm).then(this._render.bind(this));
},
//--------------------------------------------------------------------------
@@ -231,21 +231,21 @@ var SearchPanel = Widget.extend({
* Fetch values for each category. This is done only once, at startup.
*
* @private
- * @returns {$.Promise} resolved when all categories have been fetched
+ * @returns {Promise} resolved when all categories have been fetched
*/
_fetchCategories: function () {
var self = this;
- var defs = Object.keys(this.categories).map(function (categoryId) {
+ var proms = Object.keys(this.categories).map(function (categoryId) {
var category = self.categories[categoryId];
var field = self.fields[category.fieldName];
- var def;
+ var categoriesProm;
if (field.type === 'selection') {
var values = field.selection.map(function (value) {
return {id: value[0], display_name: value[1]};
});
- def = $.when(values);
+ categoriesProm = Promise.resolve(values);
} else {
- def = self._rpc({
+ categoriesProm = self._rpc({
method: 'search_panel_select_range',
model: self.model,
args: [category.fieldName],
@@ -254,18 +254,18 @@ var SearchPanel = Widget.extend({
return result.values;
});
}
- return def.then(function (values) {
+ return categoriesProm.then(function (values) {
self._createCategoryTree(categoryId, values);
});
});
- return $.when.apply($, defs);
+ return Promise.all(proms);
},
/**
* Fetch values for each filter. This is done at startup, and at each reload
* (when the controlPanel or searchPanel domain changes).
*
* @private
- * @returns {$.Promise} resolved when all filters have been fetched
+ * @returns {Promise} resolved when all filters have been fetched
*/
_fetchFilters: function () {
var self = this;
@@ -276,7 +276,7 @@ var SearchPanel = Widget.extend({
});
var categoryDomain = this._getCategoryDomain();
var filterDomain = this._getFilterDomain();
- var defs = Object.keys(this.filters).map(function (filterId) {
+ var proms = Object.keys(this.filters).map(function (filterId) {
var filter = self.filters[filterId];
return self._rpc({
method: 'search_panel_select_multi_range',
@@ -294,7 +294,7 @@ var SearchPanel = Widget.extend({
self._createFilterTree(filterId, values);
});
});
- return $.when.apply($, defs);
+ return Promise.all(proms);
},
/**
* Compute and return the domain based on the current active categories.
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index dfa7d272a49..055cd8177e3 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -65,7 +65,7 @@ var ListController = BasicController.extend({
* this method should be private, most of the code in the sidebar should be
* moved to the controller, and we should not use the getParent method...
*
- * @returns {Deferred<array[]>} a deferred that resolve to the active domain
+ * @returns {Promise<array[]>} a promise that resolve to the active domain
*/
getActiveDomain: function () {
// TODO: this method should be synchronous...
@@ -73,9 +73,9 @@ var ListController = BasicController.extend({
if (this.$('thead .o_list_record_selector input').prop('checked')) {
var searchQuery = this._controlPanel ? this._controlPanel.getSearchQuery() : {};
var record = self.model.get(self.handle, {raw: true});
- return $.when(record.getDomain().concat(searchQuery.domain || []));
+ return Promise.all(record.getDomain().concat(searchQuery.domain || []));
} else {
- return $.Deferred().resolve();
+ return Promise.resolve();
}
},
/*
@@ -143,6 +143,7 @@ var ListController = BasicController.extend({
* main buttons)
*
* @param {jQuery Node} $node
+ * @returns {Promise}
*/
renderSidebar: function ($node) {
var self = this;
@@ -180,10 +181,11 @@ var ListController = BasicController.extend({
},
actions: _.extend(this.toolbarActions, {other: other}),
});
- this.sidebar.appendTo($node);
-
- this._toggleSidebar();
+ return this.sidebar.appendTo($node).then(function() {
+ self._toggleSidebar();
+ });
}
+ return Promise.resolve();
},
/**
* Overrides to update the list of selected records
@@ -243,10 +245,11 @@ var ListController = BasicController.extend({
});
}).then(function (recordID) {
var state = self.model.get(self.handle);
- self.renderer.updateState(state, {});
- self.renderer.editRecord(recordID);
- self._updatePager();
- }).always(this._enableButtons.bind(this));
+ self.renderer.updateState(state, {})
+ .then(function () {
+ self.renderer.editRecord(recordID);
+ }).then(self._updatePager.bind(self));
+ }).then(this._enableButtons.bind(this)).guardedCatch(this._enableButtons.bind(this));
},
/**
* Archive the current selection
@@ -254,11 +257,11 @@ var ListController = BasicController.extend({
* @private
* @param {string[]} ids
* @param {boolean} archive
- * @returns {Deferred}
+ * @returns {Promise}
*/
_archive: function (ids, archive) {
if (ids.length === 0) {
- return $.when();
+ return Promise.resolve();
}
return this.model
.toggleActive(ids, !archive, this.handle)
@@ -298,7 +301,7 @@ var ListController = BasicController.extend({
* @override
* @param {string} id a basicmodel valid resource handle. It is supposed to
* be a record from the list view.
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmSave: function (id) {
var state = this.model.get(this.handle);
@@ -313,13 +316,13 @@ var ListController = BasicController.extend({
* @override
* @private
* @param {string} [recordID] - default to main recordID
- * @returns {Deferred}
+ * @returns {Promise}
*/
_discardChanges: function (recordID) {
if ((recordID || this.handle) === this.handle) {
recordID = this.renderer.getEditableRecordID();
if (recordID === null) {
- return $.when();
+ return Promise.resolve();
}
}
var self = this;
@@ -343,7 +346,7 @@ var ListController = BasicController.extend({
* @private
* @param {string} mode
* @param {string} [recordID] - default to main recordID
- * @returns {Deferred}
+ * @returns {Promise}
*/
_setMode: function (mode, recordID) {
if ((recordID || this.handle) !== this.handle) {
@@ -373,11 +376,13 @@ var ListController = BasicController.extend({
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
_update: function () {
- this._toggleSidebar();
- return this._super.apply(this, arguments);
+ var self = this;
+ return this._super.apply(this, arguments).then(function() {
+ self._toggleSidebar();
+ });
},
/**
* This helper simply makes sure that the control panel buttons matches the
@@ -471,7 +476,7 @@ var ListController = BasicController.extend({
var record = self.model.get(self.handle);
var editedRecord = record.data[ev.data.index];
self._setMode('edit', editedRecord.id)
- .done(ev.data.onSuccess);
+ .then(ev.data.onSuccess);
},
});
},
@@ -523,8 +528,8 @@ var ListController = BasicController.extend({
_onSaveLine: function (ev) {
var recordID = ev.data.recordID;
this.saveRecord(recordID)
- .done(ev.data.onSuccess)
- .fail(ev.data.onFailure);
+ .then(ev.data.onSuccess)
+ .guardedCatch(ev.data.onFailure);
},
/**
* When the current selection changes (by clicking on the checkboxes on the
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index ab5e5036dfa..4619cb86e66 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -93,7 +93,7 @@ ListRenderer.include({
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
// deliberately use the 'editable' attribute instead of '_isEditable'
@@ -171,7 +171,7 @@ ListRenderer.include({
* @param {string} id
* @param {string[]} fields
* @param {OdooEvent} ev
- * @returns {Deferred<AbstractField[]>} resolved with the list of widgets
+ * @returns {Promise<AbstractField[]>} resolved with the list of widgets
* that have been reset
*/
confirmUpdate: function (state, id, fields, ev) {
@@ -180,7 +180,7 @@ ListRenderer.include({
// store the cursor position to restore it once potential onchanges have
// been applied
var currentRowID, currentWidget, focusedElement, selectionRange;
- if (self.currentRow !== null) {
+ if (this.currentRow !== null) {
currentRowID = this.state.data[this.currentRow].id;
currentWidget = this.allFieldWidgets[currentRowID][this.currentFieldIndex];
if (currentWidget) {
@@ -212,10 +212,14 @@ ListRenderer.include({
});
var newRowIndex = _.findIndex(state.data, {id: id});
var $lastRow = $row;
- _.each(state.data, function (record, index) {
+ var defs = [];
+ self.defs = defs;
+ state.data.forEach(function (record, index) {
if (index === newRowIndex) {
return;
}
+ // FIXME: as we are manipulating the DOM directly, it will
+ // flicker if there is an async widget in the row
var $newRow = self._renderRow(record);
if (index < newRowIndex) {
$newRow.insertBefore($row);
@@ -224,20 +228,23 @@ ListRenderer.include({
$lastRow = $newRow;
}
});
- if (self.currentRow !== null) {
- self.currentRow = newRowIndex;
- return self._selectCell(newRowIndex, self.currentFieldIndex, {force: true}).then(function () {
- // restore the cursor position
- currentRowID = self.state.data[newRowIndex].id;
- currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex];
- if (currentWidget) {
- focusedElement = currentWidget.getFocusableElement().get(0);
- if (selectionRange) {
- dom.setSelectionRange(focusedElement, selectionRange);
- }
+ delete self.defs;
+ return Promise.all(defs).then(function () {
+ if (self.currentRow !== null) {
+ self.currentRow = newRowIndex;
+ return self._selectCell(newRowIndex, self.currentFieldIndex, {force: true});
+ }
+ }).then(function () {
+ // restore the cursor position
+ currentRowID = self.state.data[newRowIndex].id;
+ currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex];
+ if (currentWidget) {
+ focusedElement = currentWidget.getFocusableElement().get(0);
+ if (selectionRange) {
+ dom.setSelectionRange(focusedElement, selectionRange);
}
- });
- }
+ }
+ });
});
},
/**
@@ -294,7 +301,7 @@ ListRenderer.include({
*
* @param {string} recordID
* @param {string} mode
- * @returns {Deferred}
+ * @returns {Promise}
*/
setRowMode: function (recordID, mode) {
var self = this;
@@ -321,7 +328,7 @@ ListRenderer.include({
}
if (rowIndex < 0) {
- return $.when();
+ return Promise.resolve();
}
var editMode = (mode === 'edit');
@@ -350,7 +357,7 @@ ListRenderer.include({
options.mode = editMode ? 'edit' : 'readonly';
// Switch each cell to the new mode; note: the '_renderBodyCell'
- // function might fill the 'this.defs' variables with multiple deferred
+ // function might fill the 'this.defs' variables with multiple promise
// so we create the array and delete it after the rendering.
var defs = [];
this.defs = defs;
@@ -385,7 +392,7 @@ ListRenderer.include({
$row.toggleClass('o_selected_row', editMode);
$row.find('.o_list_record_selector input').prop('disabled', !record.res_id);
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
// necessary to trigger resize on fieldtexts
core.bus.trigger('DOM_updated');
});
@@ -399,27 +406,32 @@ ListRenderer.include({
* prevent subsequent editions. These edits would be lost, because the list
* view only saves records when unselecting a row.
*
- * @returns {Deferred} The deferred resolves if the row was unselected (and
+ * @returns {Promise} The promise resolves if the row was unselected (and
* possibly removed). If may be rejected, when the row is dirty and the
* user refuses to discard its changes.
*/
unselectRow: function () {
+ var self = this;
// Protect against calling this method when no row is selected
if (this.currentRow === null) {
- return $.when();
+ return Promise.resolve();
}
var record = this.state.data[this.currentRow];
var recordWidgets = this.allFieldWidgets[record.id];
toggleWidgets(true);
- var def = $.Deferred();
- this.trigger_up('save_line', {
- recordID: record.id,
- onSuccess: def.resolve.bind(def),
- onFailure: def.reject.bind(def),
+ var prom = new Promise(function (resolve, reject) {
+ self.trigger_up('save_line', {
+ recordID: record.id,
+ onSuccess: resolve,
+ onFailure: reject,
+ });
});
- return def.fail(toggleWidgets.bind(null, false));
+ prom.guardedCatch(function() {
+ toggleWidgets(false);
+ });
+ return prom;
function toggleWidgets(disabled) {
_.each(recordWidgets, function (widget) {
@@ -511,7 +523,7 @@ ListRenderer.include({
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
this.currentRow = null;
@@ -524,10 +536,9 @@ ListRenderer.include({
* widget.
*
* @override
- * @returns {jQueryElement}
*/
_renderBody: function () {
- var $body = this._super();
+ var $body = this._super.apply(this, arguments);
if (this.hasHandle) {
$body.sortable({
axis: 'y',
@@ -568,15 +579,13 @@ ListRenderer.include({
* on the first real column.
*
* @override
- * @returns {jQueryElement}
+ * @returns {jQueryElement[]}
*/
_renderRows: function () {
- var self = this;
var $rows = this._super();
-
if (this.addCreateLine) {
var $tr = $('<tr>');
- var colspan = self._getNumberOfCols();
+ var colspan = this._getNumberOfCols();
if (this.handleField) {
colspan = colspan - 1;
@@ -589,7 +598,7 @@ ListRenderer.include({
$tr.append($td);
$rows.push($tr);
- _.each(self.creates, function (create, index) {
+ _.each(this.creates, function (create, index) {
var $a = $('<a href="#" role="button">')
.attr('data-context', create.context)
.text(create.string);
@@ -604,7 +613,7 @@ ListRenderer.include({
/**
* @override
* @private
- * @returns {Deferred} this deferred is resolved immediately
+ * @returns {Promise} this promise is resolved immediately
*/
_renderView: function () {
var self = this;
@@ -692,13 +701,13 @@ ListRenderer.include({
* @param {boolean} [options.force=false] if true, force selecting the cell
* even if seems to be already the selected one (useful after a re-
* rendering, to reset the focus on the correct field)
- * @return {Deferred} fails if no cell could be selected
+ * @return {Promise} fails if no cell could be selected
*/
_selectCell: function (rowIndex, fieldIndex, options) {
options = options || {};
// Do nothing if the user tries to select current cell
if (!options.force && rowIndex === this.currentRow && fieldIndex === this.currentFieldIndex) {
- return $.when();
+ return Promise.resolve();
}
var wrap = options.wrap === undefined ? true : options.wrap;
@@ -707,7 +716,7 @@ ListRenderer.include({
return this._selectRow(rowIndex).then(function () {
var record = self.state.data[rowIndex];
if (fieldIndex >= (self.allFieldWidgets[record.id] || []).length) {
- return $.Deferred().reject();
+ return Promise.reject();
}
// _activateFieldWidget might trigger an onchange,
// which requires currentFieldIndex to be set
@@ -721,7 +730,7 @@ ListRenderer.include({
});
if (fieldIndex < 0) {
self.currentFieldIndex = oldFieldIndex;
- return $.Deferred().reject();
+ return Promise.reject();
}
self.currentFieldIndex = fieldIndex;
});
@@ -730,12 +739,12 @@ ListRenderer.include({
* Activates the row at the given row index.
*
* @param {integer} rowIndex
- * @returns {Deferred}
+ * @returns {Promise}
*/
_selectRow: function (rowIndex) {
// Do nothing if already selected
if (rowIndex === this.currentRow) {
- return $.when();
+ return Promise.resolve();
}
// To select a row, the currently selected one must be unselected first
@@ -745,15 +754,15 @@ ListRenderer.include({
// The row to selected doesn't exist anymore (probably because
// an onchange triggered when unselecting the previous one
// removes rows)
- return $.Deferred().reject();
+ return Promise.reject();
}
// Notify the controller we want to make a record editable
- var def = $.Deferred();
- self.trigger_up('edit_line', {
- index: rowIndex,
- onSuccess: def.resolve.bind(def),
+ return new Promise(function (resolve) {
+ self.trigger_up('edit_line', {
+ index: rowIndex,
+ onSuccess: resolve,
+ });
});
- return def;
});
},
@@ -778,7 +787,7 @@ ListRenderer.include({
// but we do want to unselect current row
var self = this;
this.unselectRow().then(function () {
- self.trigger_up('add_record', {context: ev.currentTarget.dataset.context && [ev.currentTarget.dataset.context]}); // TODO write a test, the deferred was not considered
+ self.trigger_up('add_record', {context: ev.currentTarget.dataset.context && [ev.currentTarget.dataset.context]}); // TODO write a test, the promise was not considered
});
},
/**
@@ -864,12 +873,13 @@ ListRenderer.include({
* @param {OdooEvent} ev
*/
_onNavigationMove: function (ev) {
+ var self = this;
ev.stopPropagation(); // stop the event, the action is done by this renderer
switch (ev.data.direction) {
case 'previous':
if (this.currentFieldIndex > 0) {
this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false})
- .fail(this._moveToPreviousLine.bind(this));
+ .guardedCatch(this._moveToPreviousLine.bind(this));
} else {
this._moveToPreviousLine();
}
@@ -882,7 +892,7 @@ ListRenderer.include({
if (column.attrs.name === lastWidget.name) {
if (this.currentRow + 1 < this.state.data.length) {
this._selectCell(this.currentRow+1, 0, {wrap:false})
- .fail(this._moveToNextLine.bind(this));
+ .guardedCatch(this._moveToNextLine.bind(this));
} else {
var currentRowData = this.state.data[this.currentRow];
if (currentRowData.isDirty(currentRowData.id)) {
@@ -895,7 +905,7 @@ ListRenderer.include({
} else {
if (this.currentFieldIndex + 1 < this.columns.length) {
this._selectCell(this.currentRow, this.currentFieldIndex + 1, {wrap: false})
- .fail(this._moveToNextLine.bind(this));
+ .guardedCatch(this._moveToNextLine.bind(this));
} else {
this._moveToNextLine();
}
@@ -911,8 +921,10 @@ ListRenderer.include({
ev.data.originalEvent.stopPropagation();
this.trigger_up('discard_changes', {
recordID: ev.target.dataPointID,
+ onSuccess: function () {
+ self.$('.o_field_x2many_list_row_add a:first').focus();
+ }
});
- this.$('.o_field_x2many_list_row_add a:first').focus();
break;
}
},
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index ee081aa3c5b..760ab9ed7a8 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -250,9 +250,10 @@ var ListRenderer = BasicRenderer.extend({
* @returns {jQueryElement} a jquery element <tbody>
*/
_renderBody: function () {
+ var self = this;
var $rows = this._renderRows();
while ($rows.length < 4) {
- $rows.push(this._renderEmptyRow());
+ $rows.push(self._renderEmptyRow());
}
return $('<tbody>').append($rows);
},
@@ -306,7 +307,6 @@ var ListRenderer = BasicRenderer.extend({
}
if (node.attrs.widget || (options && options.renderWidgets)) {
var $el = this._renderFieldWidget(node, record, _.pick(options, 'mode'));
- this._handleAttributes($el, node);
return $td.append($el);
}
var name = node.attrs.name;
@@ -395,7 +395,7 @@ var ListRenderer = BasicRenderer.extend({
*
* @private
* @param {Object} group
- * @returns {JQueryElement} the pager's $el
+ * @returns {jQueryElement} the pager's $el
*/
_renderGroupPager: function (group) {
var pager = new Pager(this, group.count, group.offset + 1, group.limit);
@@ -416,9 +416,14 @@ var ListRenderer = BasicRenderer.extend({
// register the pager so that it can be destroyed on next rendering
this.pagers.push(pager);
- var fragment = document.createDocumentFragment();
- pager.appendTo(fragment); // starts the pager
- return pager.$el;
+ var pagerProm = pager._widgetRenderAndInsert(function () {}); // start the pager
+ this.defs.push(pagerProm);
+ var $el = $('<div>');
+ pagerProm.then(function () {
+ $el.replaceWith(pager.$el);
+ });
+
+ return $el;
},
/**
* Render the row that represent a group
@@ -514,10 +519,9 @@ var ListRenderer = BasicRenderer.extend({
* with the name of each fields
*
* @private
- * @param {boolean} isGrouped
* @returns {jQueryElement} a <thead> element
*/
- _renderHeader: function (isGrouped) {
+ _renderHeader: function () {
var $tr = $('<tr>')
.append(_.map(this.columns, this._renderHeaderCell.bind(this)));
if (this.hasSelectors) {
@@ -585,11 +589,9 @@ var ListRenderer = BasicRenderer.extend({
*/
_renderRow: function (record) {
var self = this;
- this.defs = []; // TODO maybe wait for those somewhere ?
- var $cells = _.map(this.columns, function (node, index) {
+ var $cells = this.columns.map(function (node, index) {
return self._renderBodyCell(record, node, index, { mode: 'readonly' });
});
- delete this.defs;
var $tr = $('<tr/>', { class: 'o_data_row' })
.data('id', record.id)
@@ -608,7 +610,7 @@ var ListRenderer = BasicRenderer.extend({
* @returns {jQueryElement[]} a list of <tr>
*/
_renderRows: function () {
- return _.map(this.state.data, this._renderRow.bind(this));
+ return this.state.data.map(this._renderRow.bind(this));
},
/**
* A 'selector' is the small checkbox on the left of a record in a list
@@ -638,52 +640,60 @@ var ListRenderer = BasicRenderer.extend({
*
* @override
* @private
- * returns {Deferred} this deferred is resolved immediately
+ * @returns {Promise} resolved when the view has been rendered
*/
_renderView: function () {
var self = this;
- this.$el
- .removeClass('table-responsive')
- .empty();
-
- // destroy the previously instantiated pagers, if any
- _.invoke(this.pagers, 'destroy');
+ var oldPagers = this.pagers;
this.pagers = [];
- var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp;
// display the no content helper if there is no data to display
+ var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp;
if (displayNoContentHelper) {
+ // destroy the previously instantiated pagers, if any
+ _.invoke(oldPagers, 'destroy');
+
+ this.$el.removeClass('table-responsive');
this.$el.html(this._renderNoContentHelper());
- return this._super();
+ return this._super.apply(this, arguments);
}
- var $table = $('<table>').addClass('o_list_view table table-sm table-hover table-striped');
- this.$el.addClass('table-responsive')
- .append($table);
+ var orderedBy = this.state.orderedBy;
+ this.hasHandle = orderedBy.length === 0 || orderedBy[0].name === this.handleField;
this._computeAggregates();
+
+ var $table = $('<table>').addClass('o_list_view table table-sm table-hover table-striped');
$table.toggleClass('o_list_view_grouped', this.isGrouped);
$table.toggleClass('o_list_view_ungrouped', !this.isGrouped);
- this.hasHandle = this.state.orderedBy.length === 0 ||
- this.state.orderedBy[0].name === this.handleField;
+ var defs = [];
+ this.defs = defs;
if (this.isGrouped) {
- $table
- .append(this._renderHeader(true))
- .append(this._renderGroups(this.state.data))
- .append(this._renderFooter());
+ $table.append(this._renderHeader());
+ $table.append(this._renderGroups(this.state.data));
+ $table.append(this._renderFooter());
+
} else {
- $table
- .append(this._renderHeader())
- .append(this._renderBody())
- .append(this._renderFooter());
- }
- if (this.selection.length) {
- var $checked_rows = this.$('tr').filter(function (index, el) {
- return _.contains(self.selection, $(el).data('id'));
- });
- $checked_rows.find('.o_list_record_selector input').prop('checked', true);
+ $table.append(this._renderHeader());
+ $table.append(this._renderBody());
+ $table.append(this._renderFooter());
}
- return this._super();
+ delete this.defs;
+
+ var prom = Promise.all(defs).then(function () {
+ // destroy the previously instantiated pagers, if any
+ _.invoke(oldPagers, 'destroy');
+
+ self.$el.addClass('table-responsive').html($table);
+
+ if (self.selection.length) {
+ var $checked_rows = self.$('tr').filter(function (index, el) {
+ return _.contains(self.selection, $(el).data('id'));
+ });
+ $checked_rows.find('.o_list_record_selector input').prop('checked', true);
+ }
+ });
+ return Promise.all([this._super.apply(this, arguments), prom]);
},
/**
* Each line can be decorated according to a few simple rules. The arch
diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js
index 3f195c1ea24..a4a2bae8fb0 100644
--- a/addons/web/static/src/js/views/pivot/pivot_model.js
+++ b/addons/web/static/src/js/views/pivot/pivot_model.js
@@ -45,10 +45,10 @@ var PivotModel = AbstractModel.extend({
/**
* Close a header. This method is actually synchronous, but returns a
- * deferred.
+ * promise.
*
* @param {any} headerID
- * @returns {Deferred}
+ * @returns {Promise}
*/
closeHeader: function (headerID) {
var header = this.data.headers[headerID];
@@ -58,7 +58,7 @@ var PivotModel = AbstractModel.extend({
header.root.groupbys.splice(newGroupbyLength);
},
/**
- * @returns {Deferred}
+ * @returns {Promise}
*/
expandAll: function () {
return this._loadData();
@@ -118,8 +118,8 @@ var PivotModel = AbstractModel.extend({
});
}));
}
- return $.when.apply(null, defs).then(function () {
- var results = Array.prototype.slice.call(arguments);
+ return Promise.all(defs).then(function () {
+ var results = Array.prototype.slice.call(arguments[0]);
var data = [];
var comparisonData = [];
_.each(results, function (result) {
@@ -358,7 +358,7 @@ var PivotModel = AbstractModel.extend({
* @param {string[]} params.compare
* @param {Object} params.fields
* @param {string} params.default_order
- * @returns {Deferred}
+ * @returns {Promise}
*/
load: function (params) {
var self = this;
@@ -394,7 +394,7 @@ var PivotModel = AbstractModel.extend({
* @override
* @param {any} handle this parameter is ignored
* @param {Object} params
- * @returns {Deferred}
+ * @returns {Promise}
*/
reload: function (handle, params) {
var self = this;
@@ -507,14 +507,14 @@ var PivotModel = AbstractModel.extend({
* Toggle the active state for a given measure, then reload the data.
*
* @param {string} field
- * @returns {Deferred}
+ * @returns {Promise}
*/
toggleMeasure: function (field) {
if (_.contains(this.data.measures, field)) {
this.data.measures = _.without(this.data.measures, field);
// in this case, we already have all data in memory, no need to
// actually reload a lesser amount of information
- return $.when();
+ return Promise.resolve();
} else {
this.data.measures.push(field);
}
@@ -705,7 +705,7 @@ var PivotModel = AbstractModel.extend({
return result;
},
/**
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadData: function () {
var self = this;
@@ -755,8 +755,7 @@ var PivotModel = AbstractModel.extend({
}));
}
- return this._loadDataDropPrevious.add($.when.apply(null, defs)).then(function () {
- var results = Array.prototype.slice.call(arguments);
+ return this._loadDataDropPrevious.add(Promise.all(defs)).then(function (results) {
var data = [];
var comparisonData = [];
_.each(results, function (result) {
diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js
index 1a3c451f064..101f3734392 100644
--- a/addons/web/static/src/js/views/pivot/pivot_renderer.js
+++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js
@@ -72,7 +72,7 @@ var PivotRenderer = AbstractRenderer.extend({
/**
* @override
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
if (!this._hasContent()) {
diff --git a/addons/web/static/src/js/views/standalone_field_manager_mixin.js b/addons/web/static/src/js/views/standalone_field_manager_mixin.js
index da3cc19350d..501ecf7c012 100644
--- a/addons/web/static/src/js/views/standalone_field_manager_mixin.js
+++ b/addons/web/static/src/js/views/standalone_field_manager_mixin.js
@@ -38,7 +38,7 @@ var StandaloneFieldManagerMixin = _.extend({}, FieldManagerMixin, {
* @param {string} id basicModel Id for the changed record
* @param {string[]} fields the fields (names) that have been changed
* @param {OdooEvent} event the event that triggered the change
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmChange: function (id, fields, event) {
var result = FieldManagerMixin._confirmChange.apply(this, arguments);
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index befe4247c00..75be3a3d07e 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -128,7 +128,7 @@ var FormViewDialog = ViewDialog.extend({
text: (multi_select ? _t("Save & Close") : _t("Save")),
classes: "btn-primary",
click: function () {
- this._save().then(self.close.bind(self));
+ self._save().then(self.close.bind(self));
}
});
@@ -137,7 +137,7 @@ var FormViewDialog = ViewDialog.extend({
text: _t("Save & New"),
classes: "btn-primary",
click: function () {
- this._save()
+ self._save()
.then(self.form_view.createRecord.bind(self.form_view, self.parentID))
.then(function () {
if (!self.deletable) {
@@ -183,7 +183,7 @@ var FormViewDialog = ViewDialog.extend({
var FormView = view_registry.get('form');
var fields_view_def;
if (this.options.fields_view) {
- fields_view_def = $.when(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');
}
@@ -213,9 +213,9 @@ var FormViewDialog = ViewDialog.extend({
if (self.recordID && self.shouldSaveLocally) {
self.model.save(self.recordID, {savePoint: true});
}
- self.form_view.appendTo(fragment)
+ return self.form_view.appendTo(fragment)
.then(function () {
- self.opened().always(function () {
+ self.opened().then(function () {
var $buttons = $('<div>');
self.form_view.renderButtons($buttons);
if ($buttons.children().length) {
@@ -226,7 +226,7 @@ var FormViewDialog = ViewDialog.extend({
in_DOM: true,
});
});
- _super();
+ return _super();
});
});
@@ -255,7 +255,7 @@ var FormViewDialog = ViewDialog.extend({
/**
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_save: function () {
var self = this;
@@ -332,7 +332,7 @@ var SelectCreateDialog = ViewDialog.extend({
}
var self = this;
var _super = this._super.bind(this);
- this.loadViews(this.res_model, this.context, [[false, 'list'], [false, 'search']], {})
+ return this.loadViews(this.res_model, this.context, [[false, 'list'], [false, 'search']], {})
.then(this.setup.bind(this))
.then(function (fragment) {
self.opened().then(function () {
@@ -342,9 +342,8 @@ var SelectCreateDialog = ViewDialog.extend({
});
self.set_buttons(self.__buttons);
});
- _super();
+ return _super();
});
- return this;
},
setup: function (fieldsViews) {
diff --git a/addons/web/static/src/js/views/view_utils.js b/addons/web/static/src/js/views/view_utils.js
index 94485c1401a..ea2270ce466 100644
--- a/addons/web/static/src/js/views/view_utils.js
+++ b/addons/web/static/src/js/views/view_utils.js
@@ -63,7 +63,7 @@ var viewUtils = {
*/
renderButtonFromNode: function (node, options) {
var btnOptions = {
- attrs: _.omit(node.attrs, 'icon', 'string', 'type', 'attrs', 'modifiers', 'options'),
+ attrs: _.omit(node.attrs, 'icon', 'string', 'type', 'attrs', 'modifiers', 'options', 'effect'),
icon: node.attrs.icon,
};
if (options && options.extraClass) {