summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
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
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')
-rw-r--r--addons/web/static/src/js/_deprecated/data.js75
-rw-r--r--addons/web/static/src/js/apps.js113
-rw-r--r--addons/web/static/src/js/boot.js108
-rw-r--r--addons/web/static/src/js/chrome/abstract_action.js4
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js11
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js73
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js105
-rw-r--r--addons/web/static/src/js/chrome/action_manager_report.js57
-rw-r--r--addons/web/static/src/js/chrome/action_mixin.js8
-rw-r--r--addons/web/static/src/js/chrome/loading.js2
-rw-r--r--addons/web/static/src/js/chrome/menu.js12
-rw-r--r--addons/web/static/src/js/chrome/pager.js7
-rw-r--r--addons/web/static/src/js/chrome/sidebar.js2
-rw-r--r--addons/web/static/src/js/chrome/systray_menu.js9
-rw-r--r--addons/web/static/src/js/chrome/user_menu.js8
-rw-r--r--addons/web/static/src/js/chrome/web_client.js65
-rw-r--r--addons/web/static/src/js/core/ajax.js325
-rw-r--r--addons/web/static/src/js/core/concurrency.js239
-rw-r--r--addons/web/static/src/js/core/dialog.js10
-rw-r--r--addons/web/static/src/js/core/dom.js21
-rw-r--r--addons/web/static/src/js/core/misc.js4
-rw-r--r--addons/web/static/src/js/core/mixins.js31
-rw-r--r--addons/web/static/src/js/core/mvc.js11
-rw-r--r--addons/web/static/src/js/core/rpc.js2
-rw-r--r--addons/web/static/src/js/core/service_mixins.js92
-rw-r--r--addons/web/static/src/js/core/session.js68
-rw-r--r--addons/web/static/src/js/core/translation.js4
-rw-r--r--addons/web/static/src/js/core/widget.js34
-rw-r--r--addons/web/static/src/js/fields/abstract_field.js49
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js23
-rw-r--r--addons/web/static/src/js/fields/field_registry.js2
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js257
-rw-r--r--addons/web/static/src/js/fields/upgrade_fields.js2
-rw-r--r--addons/web/static/src/js/public/public_root.js14
-rw-r--r--addons/web/static/src/js/public/public_widget.js6
-rw-r--r--addons/web/static/src/js/report/client_action.js4
-rw-r--r--addons/web/static/src/js/services/ajax_service.js25
-rw-r--r--addons/web/static/src/js/services/core.js12
-rw-r--r--addons/web/static/src/js/services/crash_manager.js2
-rw-r--r--addons/web/static/src/js/services/data_manager.js16
-rw-r--r--addons/web/static/src/js/services/report_service.js2
-rw-r--r--addons/web/static/src/js/tools/debug_manager.js13
-rw-r--r--addons/web/static/src/js/tools/test_menus.js109
-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
-rw-r--r--addons/web/static/src/js/widgets/attach_document.js41
-rw-r--r--addons/web/static/src/js/widgets/change_password.js4
-rw-r--r--addons/web/static/src/js/widgets/data_export.js38
-rw-r--r--addons/web/static/src/js/widgets/domain_selector.js25
-rw-r--r--addons/web/static/src/js/widgets/domain_selector_dialog.js4
-rw-r--r--addons/web/static/src/js/widgets/iframe_widget.js2
-rw-r--r--addons/web/static/src/js/widgets/model_field_selector.js24
-rw-r--r--addons/web/static/src/js/widgets/name_and_signature.js10
-rw-r--r--addons/web/static/src/js/widgets/pie_chart.js2
-rw-r--r--addons/web/static/src/js/widgets/switch_company_menu.js12
95 files changed, 2049 insertions, 1856 deletions
diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js
index 984f0bbaddb..acac959fcc8 100644
--- a/addons/web/static/src/js/_deprecated/data.js
+++ b/addons/web/static/src/js/_deprecated/data.js
@@ -101,7 +101,7 @@ var Query = Class.extend({
* Fetches the first record matching the query, or null
*
* @param {Object} [options] additional options for the rpc() method
- * @returns {jQuery.Deferred<Object|null>}
+ * @returns {Promise<Object|null>}
*/
first: function (options) {
var self = this;
@@ -115,7 +115,7 @@ var Query = Class.extend({
* Fetches all records matching the query
*
* @param {Object} [options] additional options for the rpc() method
- * @returns {jQuery.Deferred<Array<>>}
+ * @returns {Promise<Array<>>}
*/
all: function (options) {
return this._execute(options);
@@ -123,10 +123,10 @@ var Query = Class.extend({
/**
* Fetches the number of records matching the query in the database
*
- * @returns {jQuery.Deferred<Number>}
+ * @returns {Promise<Number>}
*/
count: function () {
- if (this._count !== undefined) { return $.when(this._count); }
+ if (this._count !== undefined) { return Promise.resolve(this._count); }
return this._model.call(
'search_count', [this._filter], {
context: this._model.context(this._context)});
@@ -367,11 +367,12 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* @param {Array} ids identifiers of the records to read
* @param {Array} [fields] fields to read and return, by default all fields are returned
* @param {Object} [options]
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
read_ids: function (ids, fields, options) {
- if (_.isEmpty(ids))
- return $.Deferred().resolve([]);
+ if (_.isEmpty(ids)) {
+ return Promise.resolve([]);
+ }
options = options || {};
var method = 'read';
@@ -405,7 +406,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* @params {Object} [options]
* @param {Number} [options.offset=0] The index from which selected records should be returned
* @param {Number} [options.limit=null] The maximum number of records to return
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
read_slice: function (fields, options) {
var self = this;
@@ -414,21 +415,23 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
.limit(options.limit || false)
.offset(options.offset || 0)
.all();
- return this.orderer.add(query).done(function (records) {
+ var prom = this.orderer.add(query);
+ prom.then(function (records) {
self.ids = _(records).pluck('id');
});
+ return prom;
},
/**
* Reads the current dataset record (from its index)
*
* @params {Array} [fields] fields to read and return, by default all fields are returned
* @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
read_index: function (fields, options) {
options = options || {};
return this.read_ids([this.ids[this.index]], fields, options).then(function (records) {
- if (_.isEmpty(records)) { return $.Deferred().reject().promise(); }
+ if (_.isEmpty(records)) { return Promise.reject(); }
return records[0];
});
},
@@ -437,7 +440,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
*
* @param {Array} [fields] fields to get default values for, by default all defaults are read
* @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
default_get: function (fields, options) {
options = options || {};
@@ -451,15 +454,17 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* @param {Object} options Dictionary that can contain the following keys:
* - readonly_fields: Values from readonly fields that were updated by
* on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
create: function (data, options) {
var self = this;
- return this._model.call('create', [data], {
+ var prom = this._model.call('create', [data], {
context: this.get_context()
- }).done(function () {
+ });
+ prom.then(function () {
self.trigger('dataset_changed', data, options);
});
+ return prom;
},
/**
* Saves the provided data in an existing db record
@@ -470,16 +475,18 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* - context: The context to use in the server-side call.
* - readonly_fields: Values from readonly fields that were updated by
* on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
write: function (id, data, options) {
options = options || {};
var self = this;
- return this._model.call('write', [[id], data], {
+ var prom = this._model.call('write', [[id], data], {
context: this.get_context(options.context)
- }).done(function () {
+ });
+ prom.then(function () {
self.trigger('dataset_changed', id, data, options);
});
+ return prom;
},
/**
* Deletes an existing record from the database
@@ -488,11 +495,13 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
*/
unlink: function (ids) {
var self = this;
- return this._model.call('unlink', [ids], {
+ var prom = this._model.call('unlink', [ids], {
context: this.get_context()
- }).done(function () {
+ });
+ prom.then(function () {
self.trigger('dataset_changed', ids);
});
+ return prom;
},
/**
* Calls an arbitrary RPC method
@@ -501,7 +510,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* @param {Array} [args] arguments to pass to the method
* @param {Function} callback
* @param {Function} error_callback
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
call: function (method, args) {
return this._model.call(method, args);
@@ -511,7 +520,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
*
* @param {String} method
* @param {Array} [args]
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
call_button: function (method, args) {
return this._model.call_button(method, args);
@@ -520,7 +529,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* Fetches the "readable name" for records, based on intrinsic rules
*
* @param {Array} ids
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
name_get: function (ids) {
return this._model.call('name_get', [ids], {context: this.get_context()});
@@ -532,7 +541,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* @param {String} [operator='ilike'] matching operator to use with the provided name value
* @param {Number} [limit=0] maximum number of matches to return
* @param {Function} callback function to call with name_search result
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
name_search: function (name, domain, operator, limit) {
return this._model.call('name_search', {
@@ -603,7 +612,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
* Resequence records.
*
* @param {Array} ids identifiers of the records to resequence
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
resequence: function (ids, options) {
options = options || {};
@@ -643,7 +652,7 @@ var DataSetStatic = DataSet.extend({
unlink: function (ids) {
this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
this.trigger('unlink', ids);
- return $.Deferred().resolve({result: true});
+ return Promise.resolve({result: true});
},
});
@@ -674,7 +683,7 @@ var DataSetSearch = DataSet.extend({
* @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain
* @param {Number} [options.offset=0] The index from which selected records should be returned
* @param {Number} [options.limit=null] The maximum number of records to return
- * @returns {$.Deferred}
+ * @returns {Promise}
*/
read_slice: function (fields, options) {
options = options || {};
@@ -686,11 +695,13 @@ var DataSetSearch = DataSet.extend({
.limit(options.limit || false);
q = q.order_by.apply(q, this._sort);
- return this.orderer.add(q.all()).done(function (records) {
+ var prom = this.orderer.add(q.all());
+ prom.then(function (records) {
// FIXME: not sure about that one, *could* have discarded count
- q.count().done(function (count) { self._length = count; });
+ q.count().then(function (count) { self._length = count; });
self.ids = _(records).pluck('id');
});
+ return prom;
},
get_domain: function (other_domain) {
return this._model.domain(other_domain);
@@ -717,10 +728,12 @@ var DataSetSearch = DataSet.extend({
},
unlink: function (ids, callback, error_callback) {
var self = this;
- return this._super(ids).done(function () {
+ var prom = this._super(ids);
+ prom.then(function () {
self.remove_ids( ids);
self.trigger("dataset_changed", ids, callback, error_callback);
});
+ return prom;
},
size: function () {
if (this._length !== null) {
@@ -801,7 +814,7 @@ var Model = Class.extend({
* @param {Array} [args] positipyEvalonal arguments
* @param {Object} [kwargs] keyword arguments
* @param {Object} [options] additional options for the rpc() method
- * @returns {jQuery.Deferred<>} call result
+ * @returns {Promise<>} call result
*/
call: function (method, args, kwargs, options) {
args = args || [];
diff --git a/addons/web/static/src/js/apps.js b/addons/web/static/src/js/apps.js
index aadcc700984..739317337ba 100644
--- a/addons/web/static/src/js/apps.js
+++ b/addons/web/static/src/js/apps.js
@@ -22,20 +22,21 @@ var Apps = AbstractAction.extend({
},
get_client: function() {
- // return the client via a deferred, resolved or rejected depending if
+ // return the client via a promise, resolved or rejected depending if
// the remote host is available or not.
var check_client_available = function(client) {
- var d = $.Deferred();
var i = new Image();
- i.onerror = function() {
- d.reject(client);
- };
- i.onload = function() {
- d.resolve(client);
- };
+ var def = new Promise(function (resolve, reject) {
+ i.onerror = function() {
+ reject(client);
+ };
+ i.onload = function() {
+ resolve(client);
+ };
+ });
var ts = new Date().getTime();
i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif?%s', client.origin, ts);
- return d.promise();
+ return def;
};
if (apps_client) {
return check_client_available(apps_client);
@@ -105,58 +106,56 @@ var Apps = AbstractAction.extend({
start: function() {
var self = this;
- var def = $.Deferred();
- self.get_client().then(function(client) {
- self.client = client;
-
- var qs = {db: client.dbname};
- if (session.debug) {
- qs.debug = session.debug;
- }
- var u = $.param.querystring(client.origin + "/apps/embed/client", qs);
- var css = {width: '100%', height: '750px'};
- self.$ifr = $('<iframe>').attr('src', u);
-
- self.uniq = _.uniqueId('apps');
- $(window).on("message." + self.uniq, self.proxy('_on_message'));
-
- self.on('message:ready', self, function(m) {
- var w = this.$ifr[0].contentWindow;
- var act = {
- type: 'ir.actions.client',
- tag: this.remote_action_tag,
- params: _.extend({}, this.params, {
- db: session.db,
- origin: session.origin,
- })
- };
- w.postMessage({type:'action', action: act}, client.origin);
- });
+ return new Promise(function (resolve, reject) {
+ self.get_client().then(function (client) {
+ self.client = client;
+
+ var qs = {db: client.dbname};
+ if (session.debug) {
+ qs.debug = session.debug;
+ }
+ var u = $.param.querystring(client.origin + "/apps/embed/client", qs);
+ var css = {width: '100%', height: '750px'};
+ self.$ifr = $('<iframe>').attr('src', u);
+
+ self.uniq = _.uniqueId('apps');
+ $(window).on("message." + self.uniq, self.proxy('_on_message'));
+
+ self.on('message:ready', self, function(m) {
+ var w = this.$ifr[0].contentWindow;
+ var act = {
+ type: 'ir.actions.client',
+ tag: this.remote_action_tag,
+ params: _.extend({}, this.params, {
+ db: session.db,
+ origin: session.origin,
+ })
+ };
+ w.postMessage({type:'action', action: act}, client.origin);
+ });
- self.on('message:set_height', self, function(m) {
- this.$ifr.height(m.height);
- });
+ self.on('message:set_height', self, function(m) {
+ this.$ifr.height(m.height);
+ });
- self.on('message:blockUI', self, function() { framework.blockUI(); });
- self.on('message:unblockUI', self, function() { framework.unblockUI(); });
- self.on('message:warn', self, function(m) {self.do_warn(m.title, m.message, m.sticky); });
-
- self.$ifr.appendTo(self.$('.o_content')).css(css).addClass('apps-client');
-
- def.resolve();
- }, function() {
- self.do_warn(_t('Odoo Apps will be available soon'), _t('Showing locally available modules'), true);
- return self._rpc({
- route: '/web/action/load',
- params: {action_id: self.failback_action_id},
- }).then(function(action) {
- return self.do_action(action);
- }).always(function () {
- def.reject();
+ self.on('message:blockUI', self, function() { framework.blockUI(); });
+ self.on('message:unblockUI', self, function() { framework.unblockUI(); });
+ self.on('message:warn', self, function(m) {self.do_warn(m.title, m.message, m.sticky); });
+
+ self.$ifr.appendTo(self.$('.o_content')).css(css).addClass('apps-client');
+
+ resolve();
+ }, function() {
+ self.do_warn(_t('Odoo Apps will be available soon'), _t('Showing locally available modules'), true);
+ return self._rpc({
+ route: '/web/action/load',
+ params: {action_id: self.failback_action_id},
+ }).then(function(action) {
+ return self.do_action(action);
+ }).then(reject, reject);
});
});
- return def;
- },
+ }
});
var AppsUpdates = Apps.extend({
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 994b945b1d3..cf1cbaba180 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -6,8 +6,8 @@
* @name openerp
* @namespace openerp
*
- * Each module can return a deferred. In that case, the module is marked as loaded
- * only when the deferred is resolved, and its value is equal to the resolved value.
+ * Each module can return a promise. In that case, the module is marked as loaded
+ * only when the promise is resolved, and its value is equal to the resolved value.
* The module can be rejected (unloaded). This will be logged in the console as info.
*
* logs:
@@ -17,7 +17,7 @@
* Failed modules:
* A javascript error is detected
* Rejected modules:
- * The module returns a rejected deferred. It (and its dependent modules)
+ * The module returns a rejected promise. It (and its dependent modules)
* is not loaded.
* Rejected linked modules:
* Modules who depend on a rejected module
@@ -27,14 +27,14 @@
* Non loaded or failed module informations for debugging
*/
-(function() {
+(function () {
"use strict";
var jobs = [];
var factories = Object.create(null);
var job_names = [];
var job_deps = [];
- var job_deferred = [];
+ var job_promise = [];
var services = Object.create({});
@@ -45,15 +45,20 @@
var debug = ($.deparam($.param.querystring()).debug !== undefined);
var odoo = window.odoo = window.odoo || {};
+ var didLogInfoResolve;
+ var didLogInfoPromise = new Promise(function (resolve) {
+ didLogInfoResolve = resolve;
+ });
+
_.extend(odoo, {
testing: typeof QUnit === "object",
debug: debug,
remaining_jobs: jobs,
__DEBUG__: {
- didLogInfo: $.Deferred(),
+ didLogInfo: didLogInfoPromise,
get_dependencies: function (name, transitive) {
- var deps = name instanceof Array ? name: [name],
+ var deps = name instanceof Array ? name : [name],
changed;
do {
changed = false;
@@ -67,26 +72,26 @@
return deps;
},
get_dependents: function (name) {
- return _.pluck(_.where(job_deps, {from: name}), 'to');
+ return _.pluck(_.where(job_deps, { from: name }), 'to');
},
get_waited_jobs: function () {
- return _.uniq(_.map(jobs, function(job) {return job.name;}));
+ return _.uniq(_.map(jobs, function (job) { return job.name; }));
},
get_missing_jobs: function () {
var self = this;
var waited = this.get_waited_jobs();
var missing = [];
- _.each(waited, function(job) {
- _.each(self.get_dependencies(job), function(job) {
+ _.each(waited, function (job) {
+ _.each(self.get_dependencies(job), function (job) {
if (!(job in self.services)) {
missing.push(job);
}
});
});
- return _.filter(_.difference(_.uniq(missing), waited), function (job) {return !job.error;});
+ return _.filter(_.difference(_.uniq(missing), waited), function (job) { return !job.error; });
},
get_failed_jobs: function () {
- return _.filter(jobs, function (job) {return !!job.error;});
+ return _.filter(jobs, function (job) { return !!job.error; });
},
factories: factories,
services: services,
@@ -109,10 +114,10 @@
if (odoo.debug) {
if (!(deps instanceof Array)) {
- throw new Error ('Dependencies should be defined by an array', deps);
+ throw new Error('Dependencies should be defined by an array', deps);
}
if (typeof factory !== 'function') {
- throw new Error ('Factory should be defined by a function', factory);
+ throw new Error('Factory should be defined by a function', factory);
}
if (typeof name !== 'string') {
throw new Error("Invalid name definition (should be a string", name);
@@ -133,7 +138,7 @@
job_names.push(name);
_.each(deps, function (dep) {
- job_deps.push({from:dep, to:name});
+ job_deps.push({ from: dep, to: name });
});
this.process_jobs(jobs, services);
@@ -149,7 +154,7 @@
var job;
var jobdep;
- for (var k=0; k<jobs.length; k++) {
+ for (var k = 0; k < jobs.length; k++) {
debug_jobs[jobs[k].name] = job = {
dependencies: jobs[k].deps,
dependents: odoo.__DEBUG__.get_dependents(jobs[k].name),
@@ -162,10 +167,10 @@
job.rejected = jobs[k].rejected;
rejected.push(job.name);
}
- var deps = odoo.__DEBUG__.get_dependencies( job.name );
- for (var i=0; i<deps.length; i++) {
+ var deps = odoo.__DEBUG__.get_dependencies(job.name);
+ for (var i = 0; i < deps.length; i++) {
if (job.name !== deps[i] && !(deps[i] in services)) {
- jobdep = debug_jobs[deps[i]] || (deps[i] in factories && _.find(jobs, function (job) { return job.name === deps[i];}));
+ jobdep = debug_jobs[deps[i]] || (deps[i] in factories && _.find(jobs, function (job) { return job.name === deps[i]; }));
if (jobdep && jobdep.rejected) {
if (!job.rejected) {
job.rejected = [];
@@ -212,48 +217,49 @@
missing: missing,
failed: _.pluck(failed, 'name'),
};
- odoo.__DEBUG__.didLogInfo.resolve();
+ didLogInfoResolve();
},
process_jobs: function (jobs, services) {
var job;
- function process_job (job) {
+ function process_job(job) {
var require = make_require(job);
var job_exec;
- var def = $.Deferred();
- try {
- job_exec = job.factory.call(null, require);
- jobs.splice(jobs.indexOf(job), 1);
- job_deferred.push(def);
- } catch (e) {
- job.error = e;
- console.error('Error while loading ' + job.name);
- console.error(e.stack);
- }
- if (!job.error) {
- $.when(job_exec).then(
- function (data) {
- services[job.name] = data;
- def.resolve();
- odoo.process_jobs(jobs, services);
- }, function (e) {
- job.rejected = e || true;
- jobs.push(job);
- def.resolve();
- }
- );
- }
+ var def = new Promise(function (resolve) {
+ try {
+ job_exec = job.factory.call(null, require);
+ jobs.splice(jobs.indexOf(job), 1);
+ } catch (e) {
+ job.error = e;
+ console.error('Error while loading ' + job.name);
+ console.error(e.stack);
+ }
+ if (!job.error) {
+ Promise.resolve(job_exec).then(
+ function (data) {
+ services[job.name] = data;
+ resolve();
+ odoo.process_jobs(jobs, services);
+ }).guardedCatch(function (e) {
+ job.rejected = e || true;
+ jobs.push(job);
+ resolve();
+ }
+ );
+ }
+ });
+ job_promise.push(def);
}
- function is_ready (job) {
+ function is_ready(job) {
return !job.error && !job.rejected && _.every(job.factory.deps, function (name) { return name in services; });
}
- function make_require (job) {
+ function make_require(job) {
var deps = _.pick(services, job.deps);
- function require (name) {
+ function require(name) {
if (!(name in deps)) {
console.error('Undefined dependency: ', name);
} else {
@@ -278,9 +284,9 @@
// automatically log errors detected when loading modules
var log_when_loaded = function () {
_.delay(function () {
- var len = job_deferred.length;
- $.when.apply($, job_deferred).then(function () {
- if (len === job_deferred.length) {
+ var len = job_promise.length;
+ Promise.all(job_promise).then(function () {
+ if (len === job_promise.length) {
odoo.log();
} else {
log_when_loaded();
diff --git a/addons/web/static/src/js/chrome/abstract_action.js b/addons/web/static/src/js/chrome/abstract_action.js
index a5f98434ddb..1b258ccc6f6 100644
--- a/addons/web/static/src/js/chrome/abstract_action.js
+++ b/addons/web/static/src/js/chrome/abstract_action.js
@@ -104,7 +104,7 @@ var AbstractAction = Widget.extend(ActionMixin, {
};
});
}
- return $.when(def).then(function () {
+ return Promise.resolve(def).then(function () {
var controlPanelView = new self.config.ControlPanelView(params);
return controlPanelView.getController(self).then(function (controlPanel) {
self._controlPanel = controlPanel;
@@ -112,7 +112,7 @@ var AbstractAction = Widget.extend(ActionMixin, {
});
});
}
- return $.when();
+ return Promise.resolve();
},
/**
* @override
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index 7082f26e69d..68ac157868d 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -78,7 +78,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
if (event.data.on_success) {
event.data.on_success(result);
}
- }).fail(function (result) {
+ }).guardedCatch(function (result) {
if (event.data.on_fail) {
event.data.on_fail(result);
}
@@ -116,17 +116,17 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
.then(function () {
self.$el.toggleClass('o_rtl', _t.database.parameters.direction === "rtl");
self.bind_events();
- return $.when(
+ return Promise.all([
self.set_action_manager(),
self.set_loading()
- );
+ ]);
}).then(function () {
if (session.session_is_valid()) {
return self.show_application();
} else {
// database manager needs the webclient to keep going even
// though it has no valid session
- return $.when();
+ return Promise.resolve();
}
}).then(function () {
// Listen to 'scroll' event and propagate it on main bus
@@ -145,12 +145,13 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function () {
$(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
});
+ // TODO: this handler seems useless since 11.0, should be removed
this.$el.on('click', '.oe_dropdown_toggle', function (ev) {
ev.preventDefault();
var $toggle = $(this);
var doc_width = $(document).width();
var $menu = $toggle.siblings('.oe_dropdown_menu');
- $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
+ $menu = $menu.length >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
var state = $menu.is('.oe_opened');
setTimeout(function () {
// Do not alter propagation
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index 107c6e314eb..0537b45deb5 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -92,7 +92,7 @@ var ActionManager = Widget.extend({
* will be restored. It ensures that the current controller can be left (for
* instance, that it has no unsaved changes).
*
- * @returns {Deferred} resolved if the current controller can be left,
+ * @returns {Promise} resolved if the current controller can be left,
* rejected otherwise.
*/
clearUncommittedChanges: function () {
@@ -100,7 +100,7 @@ var ActionManager = Widget.extend({
if (currentController) {
return currentController.widget.canBeRemoved();
}
- return $.when();
+ return Promise.resolve();
},
/**
* This is the entry point to execute Odoo actions, given as an ID in
@@ -123,7 +123,7 @@ var ActionManager = Widget.extend({
* is useful when we come from a loadState())
* @param {boolean} [options.replace_last_action=false] set to true to
* replace last part of the breadcrumbs with the action
- * @return {$.Deferred<Object>} resolved with the action when the action is
+ * @return {Promise<Object>} resolved with the action when the action is
* loaded and appended to the DOM ; rejected if the action can't be
* executed (e.g. if doAction has been called to execute another action
* before this one was complete).
@@ -155,7 +155,7 @@ var ActionManager = Widget.extend({
});
}
- return this.dp.add($.when(def)).then(function () {
+ return this.dp.add(Promise.resolve(def)).then(function () {
// action.target 'main' is equivalent to 'current' except that it
// also clears the breadcrumbs
options.clear_breadcrumbs = action.target === 'main' ||
@@ -211,12 +211,12 @@ var ActionManager = Widget.extend({
* @param {Object} state
* @param {integer|string} [state.action] the action to execute (given its
* id or tag for client actions)
- * @returns {Deferred} resolved when the UI has been updated
+ * @returns {Promise} resolved when the UI has been updated
*/
loadState: function (state) {
var action;
if (!state.action) {
- return $.when();
+ return Promise.resolve();
}
if (_.isString(state.action) && core.action_registry.contains(state.action)) {
action = {
@@ -299,7 +299,7 @@ var ActionManager = Widget.extend({
* @param {string} [action.target="current"] set to "new" to render the
* controller in a dialog
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the controller is started and appended
+ * @returns {Promise} resolved when the controller is started and appended
*/
_executeAction: function (action, options) {
var self = this;
@@ -338,7 +338,7 @@ var ActionManager = Widget.extend({
return action;
})
- .fail(function () {
+ .guardedCatch(function () {
self._removeAction(action.jsID);
});
},
@@ -349,7 +349,7 @@ var ActionManager = Widget.extend({
* @private
* @param {Object} action
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the controller is rendered inside a
+ * @returns {Promise} resolved when the controller is rendered inside a
* dialog appended to the DOM
*/
_executeActionInDialog: function (action, options) {
@@ -401,7 +401,7 @@ var ActionManager = Widget.extend({
return action;
});
- }).fail(function () {
+ }).guardedCatch(function () {
self._removeAction(action.jsID);
});
},
@@ -412,14 +412,14 @@ var ActionManager = Widget.extend({
* @param {Object} action the description of the action to execute
* @param {string} action.tag the key of the action in the action_registry
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the client action has been executed
+ * @returns {Promise} resolved when the client action has been executed
*/
_executeClientAction: function (action, options) {
var self = this;
var ClientAction = core.action_registry.get(action.tag);
if (!ClientAction) {
console.error("Could not find client action " + action.tag, action);
- return $.Deferred().reject();
+ return Promise.reject();
}
if (!(ClientAction.prototype instanceof AbstractAction)) {
console.warn('The client action ' + action.tag + ' should be an instance of AbstractAction!');
@@ -431,7 +431,7 @@ var ActionManager = Widget.extend({
if (next) {
return this.doAction(next, options);
}
- return $.when();
+ return Promise.resolve();
}
var controllerID = _.uniqueId('controller_');
@@ -449,12 +449,14 @@ var ActionManager = Widget.extend({
};
this.controllers[controllerID] = controller;
action.controllerID = controllerID;
- return this._executeAction(action, options).done(function () {
+ var prom = this._executeAction(action, options);
+ prom.then(function () {
// AAB: this should be done automatically in AbstractAction, so that
// it can be overriden by actions that have specific stuff to push
// (e.g. Discuss, Views)
self._pushState(controllerID, {});
});
+ return prom;
},
/**
* Executes actions of type 'ir.actions.act_window_close', i.e. closes the
@@ -469,7 +471,7 @@ var ActionManager = Widget.extend({
* @param {Object} [action.effect] effect to show up, e.g. rainbow man.
* @param {Object} [action.infos] infos on performing the close action.
* Useful for providing some context for the `on_close` handler.
- * @returns {Deferred} resolved immediately
+ * @returns {Promise} resolved immediately
*/
_executeCloseAction: function (action, options) {
var result;
@@ -484,7 +486,7 @@ var ActionManager = Widget.extend({
this.trigger_up('show_effect', action.effect);
}
- return $.when(result);
+ return Promise.resolve(result);
},
/**
* Executes actions of type 'ir.actions.server'.
@@ -494,7 +496,7 @@ var ActionManager = Widget.extend({
* @param {integer} action.id the db ID of the action to execute
* @param {Object} [action.context]
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the action has been executed
+ * @returns {Promise} resolved when the action has been executed
*/
_executeServerAction: function (action, options) {
var self = this;
@@ -520,7 +522,7 @@ var ActionManager = Widget.extend({
* @param {string} [action.target] set to 'self' to redirect in the current page,
* redirects to a new page by default
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the redirection is done (immediately
+ * @returns {Promise} resolved when the redirection is done (immediately
* when redirecting to a new page)
*/
_executeURLAction: function (action, options) {
@@ -531,7 +533,7 @@ var ActionManager = Widget.extend({
if (action.target === 'self') {
framework.redirect(url);
- return $.Deferred();
+ return Promise.resolve();
} else {
var w = window.open(url, '_blank');
if (!w || w.closed || typeof w.closed === 'undefined') {
@@ -544,7 +546,7 @@ var ActionManager = Widget.extend({
options.on_close();
- return $.when();
+ return Promise.resolve();
},
/**
* Returns a description of the controllers in the given controller stack.
@@ -653,14 +655,14 @@ var ActionManager = Widget.extend({
* @param {Object} action
* @param {string} action.type
* @param {Object} options
- * @returns {Deferred} resolved when the action has been executed ; rejected
+ * @returns {Promise} resolved when the action has been executed ; rejected
* if the type of action isn't supported, or if the action can't be
* executed
*/
_handleAction: function (action, options) {
if (!action.type) {
console.error("No type for action", action);
- return $.Deferred().reject();
+ return Promise.reject();
}
switch (action.type) {
case 'ir.actions.act_url':
@@ -674,7 +676,7 @@ var ActionManager = Widget.extend({
default:
console.error("The ActionManager can't handle actions of type " +
action.type, action);
- return $.Deferred().reject();
+ return Promise.reject();
}
},
/**
@@ -749,16 +751,17 @@ var ActionManager = Widget.extend({
* @private
* @param {integer|string} action's ID or xml ID
* @param {Object} context
- * @returns {Deferred<Object>} resolved with the description of the action
+ * @returns {Promise<Object>} resolved with the description of the action
*/
_loadAction: function (actionID, context) {
- var def = $.Deferred();
- this.trigger_up('load_action', {
- actionID: actionID,
- context: context,
- on_success: def.resolve.bind(def),
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ self.trigger_up('load_action', {
+ actionID: actionID,
+ context: context,
+ on_success: resolve,
+ });
});
- return def;
},
/**
* Preprocesses the action before it is handled by the ActionManager
@@ -815,20 +818,20 @@ var ActionManager = Widget.extend({
*
* @private
* @param {string} controllerID
- * @returns {Deferred} resolved when the controller has been restored
+ * @returns {Promise} resolved when the controller has been restored
*/
_restoreController: function (controllerID) {
var self = this;
var controller = this.controllers[controllerID];
// AAB: AbstractAction should define a proper hook to execute code when
- // it is restored (other than do_show), and it should return a deferred
+ // it is restored (other than do_show), and it should return a promise
var action = this.actions[controller.actionID];
var def;
if (action.on_reverse_breadcrumb) {
def = action.on_reverse_breadcrumb();
}
- return $.when(def).then(function () {
- return $.when(controller.widget.do_show()).then(function () {
+ return Promise.resolve(def).then(function () {
+ return Promise.resolve(controller.widget.do_show()).then(function () {
var index = _.indexOf(self.controllerStack, controllerID);
self._pushController(controller, index);
});
@@ -844,7 +847,7 @@ var ActionManager = Widget.extend({
*
* @private
* @param {Object} controller
- * @returns {Deferred<Object>} resolved with the controller when it is ready
+ * @returns {Promise<Object>} resolved with the controller when it is ready
*/
_startController: function (controller) {
var fragment = document.createDocumentFragment();
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js
index 106f9f2aa47..d00b3a49eb2 100644
--- a/addons/web/static/src/js/chrome/action_manager_act_window.js
+++ b/addons/web/static/src/js/chrome/action_manager_act_window.js
@@ -144,7 +144,7 @@ ActionManager.include({
* @param {integer} [options.index=0] the controller's index in the stack
* @param {boolean} [options.lazy=false] set to true to differ the
* initialization of the controller's widget
- * @returns {Deferred<Object>} resolved with the created controller
+ * @returns {Promise<Object>} resolved with the created controller
*/
_createViewController: function (action, viewType, viewOptions, options) {
var self = this;
@@ -153,7 +153,7 @@ ActionManager.include({
// the requested view type isn't specified in the action (e.g.
// action with list view only, user clicks on a row in the list, it
// tries to switch to form view)
- return $.Deferred().reject();
+ return Promise.reject();
}
options = options || {};
@@ -186,26 +186,31 @@ ActionManager.include({
// communication with trigger_up
controllerID: controllerID,
});
+ var rejection;
var view = new viewDescr.Widget(viewDescr.fieldsView, viewOptions);
- var def = $.Deferred();
- action.controllers[viewType] = def;
- view.getController(this).then(function (widget) {
- if (def.state() === 'rejected') {
- // the deferred has been rejected meanwhile, meaning that
- // the action has been removed, so simply destroy the widget
- widget.destroy();
- } else {
- controller.widget = widget;
- def.resolve(controller);
- }
- }).fail(def.reject.bind(def));
- def.fail(function () {
+ var def = new Promise(function (resolve, reject) {
+ rejection = reject;
+ view.getController(self).then(function (widget) {
+ if (def.rejected) {
+ // the promise has been rejected meanwhile, meaning that
+ // the action has been removed, so simply destroy the widget
+ widget.destroy();
+ } else {
+ controller.widget = widget;
+ resolve(controller);
+ }
+ }).guardedCatch(reject);
+ });
+ // Need to define an reject property to call it into _destroyWindowAction
+ def.reject = rejection;
+ def.guardedCatch(function () {
+ def.rejected = true;
delete self.controllers[controllerID];
});
+ action.controllers[viewType] = def;
} else {
- action.controllers[viewType] = $.Deferred().resolve(controller);
+ action.controllers[viewType] = Promise.resolve(controller);
}
-
return action.controllers[viewType];
},
/**
@@ -217,18 +222,22 @@ ActionManager.include({
*/
_destroyWindowAction: function (action) {
var self = this;
- _.each(action.controllers, function (controllerDef) {
+ for (var c in action.controllers) {
+ var controllerDef = action.controllers[c];
controllerDef.then(function (controller) {
delete self.controllers[controller.jsID];
if (controller.widget) {
controller.widget.destroy();
}
});
- // reject the deferred if it is not yet resolved, so that the
- // controller is correctly destroyed as soon as it is ready, and
- // its reference is removed
- controllerDef.reject();
- });
+ // If controllerDef is not resolved yet, reject it so that the
+ // controller will be correctly destroyed as soon as it'll be ready,
+ // and its reference will be removed. Lazy-loaded controllers do
+ // not have a reject function on their promise
+ if (controllerDef.reject) {
+ controllerDef.reject();
+ }
+ }
},
/**
* Executes actions of type 'ir.actions.act_window'.
@@ -239,13 +248,13 @@ ActionManager.include({
* @param {Object} options @see doAction for details
* @param {integer} [options.resID] the current res ID
* @param {string} [options.viewType] the view to open
- * @returns {Deferred} resolved when the action is appended to the DOM
+ * @returns {Promise} resolved when the action is appended to the DOM
*/
_executeWindowAction: function (action, options) {
var self = this;
return this.dp.add(this._loadViews(action)).then(function (fieldsViews) {
var views = self._generateActionViews(action, fieldsViews);
- action._views = action.views; // save the initial attribute
+ action._views = action.views; // save the initial attribute
action.views = views;
action.controlPanelFieldsView = fieldsViews.search;
action.controllers = {};
@@ -285,7 +294,7 @@ ActionManager.include({
self.controllerStack.push(lazyLoadedController.jsID);
});
}
- return self.dp.add($.when(lazyViewDef))
+ return self.dp.add(Promise.resolve(lazyViewDef))
.then(function () {
var viewOptions = {
controllerState: options.controllerState,
@@ -300,7 +309,7 @@ ActionManager.include({
action.controllerID = controller.jsID;
return self._executeAction(action, options);
})
- .fail(self._destroyWindowAction.bind(self, action));
+ .guardedCatch(self._destroyWindowAction.bind(self, action));
});
},
/**
@@ -389,7 +398,7 @@ ActionManager.include({
*
* @private
* @param {Object} action
- * @returns {Deferred}
+ * @returns {Promise}
*/
_loadViews: function (action) {
var inDialog = action.target === 'new';
@@ -443,11 +452,11 @@ ActionManager.include({
return this.clearUncommittedChanges().then(function () {
// AAB: this will be done directly in AbstractAction's restore
// function
- var def;
+ var def = Promise.resolve();
if (action.on_reverse_breadcrumb) {
def = action.on_reverse_breadcrumb();
}
- return $.when(def).then(function () {
+ return Promise.resolve(def).then(function () {
return self._switchController(action, controller.viewType);
});
});
@@ -461,14 +470,14 @@ ActionManager.include({
* @private
* @param {Object} controller the controller to switch to
* @param {Object} [viewOptions]
- * @return {Deferred} resolved when the new controller is in the DOM
+ * @return {Promise} resolved when the new controller is in the DOM
*/
_switchController: function (action, viewType, viewOptions) {
var self = this;
var view = _.findWhere(action.views, {type: viewType});
if (!view) {
// can't switch to an unknown view
- return $.Deferred().reject();
+ return Promise.reject();
}
var currentController = this.getCurrentController();
var index;
@@ -519,28 +528,30 @@ ActionManager.include({
};
var controllerDef = action.controllers[viewType];
- if (!controllerDef || controllerDef.state() === 'rejected') {
- // if the controllerDef is rejected, it probably means that the js
- // code or the requests made to the server crashed. In that case,
- // if we reuse the same deferred, then the switch to the view is
- // definitely blocked. We want to use a new controller, even though
- // it is very likely that it will recrash again. At least, it will
- // give more feedback to the user, and it could happen that one
- // record crashes, but not another.
- controllerDef = newController();
- } else {
+ if (controllerDef) {
controllerDef = controllerDef.then(function (controller) {
if (!controller.widget) {
// lazy loaded -> load it now (with same jsID)
return newController(controller.jsID);
} else {
- return $.when(controller.widget.willRestore()).then(function () {
+ return Promise.resolve(controller.widget.willRestore()).then(function () {
return controller.widget.reload(viewOptions).then(function () {
return controller;
});
});
}
+ }, function () {
+ // if the controllerDef is rejected, it probably means that the js
+ // code or the requests made to the server crashed. In that case,
+ // if we reuse the same promise, then the switch to the view is
+ // definitely blocked. We want to use a new controller, even though
+ // it is very likely that it will recrash again. At least, it will
+ // give more feedback to the user, and it could happen that one
+ // record crashes, but not another.
+ return newController();
});
+ } else {
+ controllerDef = newController();
}
return this.dp.add(controllerDef).then(function (controller) {
@@ -577,11 +588,11 @@ ActionManager.include({
var env = ev.data.env;
var context = new Context(env.context, actionData.context || {});
var recordID = env.currentID || null; // pyUtils handles null value, not undefined
- var def = $.Deferred();
+ var def;
// determine the action to execute according to the actionData
if (actionData.special) {
- def = $.when({type: 'ir.actions.act_window_close', infos: 'special'});
+ def = Promise.resolve({type: 'ir.actions.act_window_close', infos: 'special'});
} else if (actionData.type === 'object') {
// call a Python Object method, which may return an action to execute
var args = recordID ? [[recordID]] : [env.resIDs];
@@ -611,13 +622,15 @@ ActionManager.include({
active_ids: env.resIDs,
active_id: recordID,
}));
+ } else {
+ def = new Promise(function () {});
}
// use the DropPrevious to prevent from executing the handler if another
// request (doAction, switchView...) has been done meanwhile ; execute
// the fail handler if the 'call_button' or 'loadAction' failed but not
// if the request failed due to the DropPrevious,
- def.fail(ev.data.on_fail);
+ def.guardedCatch(ev.data.on_fail);
this.dp.add(def).then(function (action) {
// show effect if button have effect attribute
// rainbowman can be displayed from two places: from attribute on a button or from python
diff --git a/addons/web/static/src/js/chrome/action_manager_report.js b/addons/web/static/src/js/chrome/action_manager_report.js
index 51922cd719f..30bac14006d 100644
--- a/addons/web/static/src/js/chrome/action_manager_report.js
+++ b/addons/web/static/src/js/chrome/action_manager_report.js
@@ -40,35 +40,36 @@ ActionManager.include({
* report generation and download.
*
* @param {string} url
- * @returns {Deferred} resolved when the report has been downloaded ;
+ * @returns {Promise} resolved when the report has been downloaded ;
* rejected if something went wrong during the report generation
*/
_downloadReport: function (url) {
+ var self = this;
framework.blockUI();
- var def = $.Deferred();
- var type = 'qweb-' + url.split('/')[2];
- var blocked = !session.get_file({
- url: '/report/download',
- data: {
- data: JSON.stringify([url, type]),
- },
- success: def.resolve.bind(def),
- error: function () {
- crash_manager.rpc_error.apply(crash_manager, arguments);
- def.reject();
- },
- complete: framework.unblockUI,
+ return new Promise(function (resolve, reject) {
+ var type = 'qweb-' + url.split('/')[2];
+ var blocked = !session.get_file({
+ url: '/report/download',
+ data: {
+ data: JSON.stringify([url, type]),
+ },
+ success: resolve,
+ error: function () {
+ crash_manager.rpc_error.apply(crash_manager, arguments);
+ reject();
+ },
+ complete: framework.unblockUI,
+ });
+ if (blocked) {
+ // AAB: this check should be done in get_file service directly,
+ // should not be the concern of the caller (and that way, get_file
+ // could return a promise)
+ var message = _t('A popup window with your report was blocked. You ' +
+ 'may need to change your browser settings to allow ' +
+ 'popup windows for this page.');
+ self.do_warn(_t('Warning'), message, true);
+ }
});
- if (blocked) {
- // AAB: this check should be done in get_file service directly,
- // should not be the concern of the caller (and that way, get_file
- // could return a deferred)
- var message = _t('A popup window with your report was blocked. You ' +
- 'may need to change your browser settings to allow ' +
- 'popup windows for this page.');
- this.do_warn(_t('Warning'), message, true);
- }
- return def;
},
/**
@@ -77,7 +78,7 @@ ActionManager.include({
* @private
* @param {Object} action the description of the action to execute
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the action has been executed
+ * @returns {Promise} resolved when the action has been executed
*/
_triggerDownload: function (action, options, type){
var self = this;
@@ -97,7 +98,7 @@ ActionManager.include({
* @private
* @param {Object} action the description of the action to execute
* @param {Object} options @see doAction for details
- * @returns {Deferred} resolved when the action has been executed
+ * @returns {Promise} resolved when the action has been executed
*/
_executeReportAction: function (action, options) {
var self = this;
@@ -125,7 +126,7 @@ ActionManager.include({
} else {
console.error("The ActionManager can't handle reports of type " +
action.report_type, action);
- return $.Deferred().reject();
+ return Promise.reject();
}
},
/**
@@ -135,7 +136,7 @@ ActionManager.include({
*
* @param {Object} action
* @param {Object} options
- * @returns {Deferred} resolved when the client action has been executed
+ * @returns {Promise} resolved when the client action has been executed
*/
_executeReportClientAction: function (action, options) {
var urls = this._makeReportUrls(action);
diff --git a/addons/web/static/src/js/chrome/action_mixin.js b/addons/web/static/src/js/chrome/action_mixin.js
index 92d6716a7c0..02345e7e969 100644
--- a/addons/web/static/src/js/chrome/action_mixin.js
+++ b/addons/web/static/src/js/chrome/action_mixin.js
@@ -75,7 +75,7 @@ var ActionMixin = {
* Called by the action manager when action is restored (typically, when the
* user clicks on the action in the breadcrumb)
*
- * @returns {Deferred|undefined}
+ * @returns {Promise|undefined}
*/
willRestore: function () {},
@@ -89,14 +89,14 @@ var ActionMixin = {
* example, if the user has edited a form, maybe we should ask him if we
* can discard all his changes when we switch to another action. In that
* case, the action manager will call this method. If the returned
- * deferred is succesfully resolved, then we can destroy the current action,
+ * promise is succesfully resolved, then we can destroy the current action,
* otherwise, we need to stop.
*
- * @returns {Deferred} resolved if the action can be removed, rejected
+ * @returns {Promise} resolved if the action can be removed, rejected
* otherwise
*/
canBeRemoved: function () {
- return $.when();
+ return Promise.resolve();
},
/**
* This function is called when the current state of the action
diff --git a/addons/web/static/src/js/chrome/loading.js b/addons/web/static/src/js/chrome/loading.js
index 6c7f102231b..865aa081bf0 100644
--- a/addons/web/static/src/js/chrome/loading.js
+++ b/addons/web/static/src/js/chrome/loading.js
@@ -26,6 +26,8 @@ var Loading = Widget.extend({
this._super(parent);
this.count = 0;
this.blocked_ui = false;
+ },
+ start: function() {
core.bus.on('rpc_request', this, this.request_call);
core.bus.on("rpc_response", this, this.response_call);
core.bus.on("rpc_response_failed", this, this.response_call);
diff --git a/addons/web/static/src/js/chrome/menu.js b/addons/web/static/src/js/chrome/menu.js
index 8722724c9a6..552dfce811c 100644
--- a/addons/web/static/src/js/chrome/menu.js
+++ b/addons/web/static/src/js/chrome/menu.js
@@ -64,20 +64,22 @@ var Menu = Widget.extend({
// Apps Menu
this._appsMenu = new AppsMenu(self, this.menu_data);
- this._appsMenu.appendTo(this.$menu_apps);
+ var appsMenuProm = this._appsMenu.appendTo(this.$menu_apps);
// Systray Menu
this.systray_menu = new SystrayMenu(this);
- this.systray_menu.attachTo(this.$('.o_menu_systray'));
-
- dom.initAutoMoreMenu(this.$section_placeholder, {
+ var systrayMenuProm = this.systray_menu.attachTo(this.$('.o_menu_systray')).then(function() {
+ dom.initAutoMoreMenu(self.$section_placeholder, {
maxWidth: function () {
return self.$el.width() - (self.$menu_apps.outerWidth(true) + self.$menu_brand_placeholder.outerWidth(true) + self.systray_menu.$el.outerWidth(true));
},
sizeClass: 'SM',
+ });
});
- return this._super.apply(this, arguments);
+
+
+ return Promise.all([this._super.apply(this, arguments), appsMenuProm, systrayMenuProm]);
},
change_menu_section: function (primary_menu_id) {
if (!this.$menu_sections[primary_menu_id]) {
diff --git a/addons/web/static/src/js/chrome/pager.js b/addons/web/static/src/js/chrome/pager.js
index c7f22a6f2c9..86700d93374 100644
--- a/addons/web/static/src/js/chrome/pager.js
+++ b/addons/web/static/src/js/chrome/pager.js
@@ -28,7 +28,7 @@ var Pager = Widget.extend({
* @param {boolean} [options.can_edit] editable feature of the pager
* @param {boolean} [options.single_page_hidden] (not) to display the pager
* if only one page
- * @param {function} [options.validate] callback returning a Deferred to
+ * @param {function} [options.validate] callback returning a Promise to
* validate changes
*/
init: function (parent, size, current_min, limit, options) {
@@ -46,7 +46,7 @@ var Pager = Widget.extend({
can_edit: true, // editable
single_page_hidden: false, // displayed even if there is a single page
validate: function() {
- return $.Deferred().resolve();
+ return Promise.resolve();
},
withAccessKey: true, // can be disabled, for example, for x2m widgets
});
@@ -55,7 +55,7 @@ var Pager = Widget.extend({
/**
* Renders the pager
*
- * @returns {jQuery.Deferred}
+ * @returns {Promise}
*/
start: function () {
this.$value = this.$('.o_pager_value');
@@ -216,7 +216,6 @@ var Pager = Widget.extend({
}
self.trigger('pager_changed', _.clone(self.state));
}
- }).always(function() {
// Render the pager's new state (removes the input)
self._render();
});
diff --git a/addons/web/static/src/js/chrome/sidebar.js b/addons/web/static/src/js/chrome/sidebar.js
index 5cf6005a788..b7883cbb0f7 100644
--- a/addons/web/static/src/js/chrome/sidebar.js
+++ b/addons/web/static/src/js/chrome/sidebar.js
@@ -153,7 +153,7 @@ var Sidebar = Widget.extend({
action_id: item.action.id,
context: context,
},
- }).done(function (result) {
+ }).then(function (result) {
result.context = new Context(
result.context || {}, activeIdsContext)
.set_eval_context(context);
diff --git a/addons/web/static/src/js/chrome/systray_menu.js b/addons/web/static/src/js/chrome/systray_menu.js
index 24fd02a3736..fd214c0b650 100644
--- a/addons/web/static/src/js/chrome/systray_menu.js
+++ b/addons/web/static/src/js/chrome/systray_menu.js
@@ -16,20 +16,19 @@ var SystrayMenu = Widget.extend({
this._super(parent);
this.items = [];
this.widgets = [];
- this.load = $.Deferred();
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
var self = this;
self._super.apply(this, arguments);
self._loadItems();
- $.when.apply($, self.items).always(function () {
- self.load.resolve();
+
+ return new Promise(function (resolve, reject) {
+ Promise.all(self.items).then(resolve).guardedCatch(resolve);
});
- return self.load;
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/chrome/user_menu.js b/addons/web/static/src/js/chrome/user_menu.js
index a066b661fb2..2cc39b4d6bd 100644
--- a/addons/web/static/src/js/chrome/user_menu.js
+++ b/addons/web/static/src/js/chrome/user_menu.js
@@ -22,7 +22,7 @@ var UserMenu = Widget.extend({
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
var self = this;
@@ -36,7 +36,7 @@ var UserMenu = Widget.extend({
var $avatar = self.$('.oe_topbar_avatar');
if (!session.uid) {
$avatar.attr('src', $avatar.data('default-src'));
- return $.when();
+ return Promise.resolve();
}
var topbar_name = session.name;
if (session.debug) {
@@ -67,7 +67,7 @@ var UserMenu = Widget.extend({
.then(function (url) {
framework.redirect(url);
})
- .fail(function (result, ev){
+ .guardedCatch(function (result, ev){
ev.preventDefault();
framework.redirect('https://accounts.odoo.com/account');
});
@@ -100,7 +100,7 @@ var UserMenu = Widget.extend({
model: "res.users",
method: "action_get"
})
- .done(function (result) {
+ .then(function (result) {
result.res_id = session.uid;
self.do_action(result);
});
diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js
index 0688adc362f..392d534f372 100644
--- a/addons/web/static/src/js/chrome/web_client.js
+++ b/addons/web/static/src/js/chrome/web_client.js
@@ -111,7 +111,7 @@ return AbstractWebClient.extend({
instanciate_menu_widgets: function () {
var self = this;
- var defs = [];
+ var proms = [];
return this.load_menus().then(function (menuData) {
self.menu_data = menuData;
@@ -122,8 +122,8 @@ return AbstractWebClient.extend({
self.menu.destroy();
}
self.menu = new Menu(self, menuData);
- defs.push(self.menu.prependTo(self.$el));
- return $.when.apply($, defs);
+ proms.push(self.menu.prependTo(self.$el));
+ return Promise.all(proms);
});
},
@@ -133,7 +133,7 @@ return AbstractWebClient.extend({
on_hashchange: function (event) {
if (this._ignore_hashchange) {
this._ignore_hashchange = false;
- return $.when();
+ return Promise.resolve();
}
var self = this;
@@ -181,48 +181,51 @@ return AbstractWebClient.extend({
return this.menu_dm.add(data_manager.load_action(ev.data.action_id))
.then(function (result) {
return self.action_mutex.exec(function () {
- var completed = $.Deferred();
- var options = _.extend({}, ev.data.options, {
- clear_breadcrumbs: true,
- action_menu_id: ev.data.menu_id,
- });
- $.when(self._openMenu(result, options)).fail(function () {
- completed.resolve();
- }).done(function () {
- self._on_app_clicked_done(ev)
- .then(completed.resolve.bind(completed))
- .fail(completed.reject.bind(completed));
+ var completed = new Promise(function (resolve, reject) {
+ var options = _.extend({}, ev.data.options, {
+ clear_breadcrumbs: true,
+ action_menu_id: ev.data.menu_id,
+ });
+
+ Promise.resolve(self._openMenu(result, options))
+ .then(function() {
+ self._on_app_clicked_done(ev)
+ .then(resolve)
+ .guardedCatch(reject);
+ }).guardedCatch(function() {
+ resolve();
+ });
+ setTimeout(function () {
+ resolve();
+ }, 2000);
});
- setTimeout(function () {
- completed.resolve();
- }, 2000);
return completed;
});
});
},
_on_app_clicked_done: function (ev) {
core.bus.trigger('change_menu_section', ev.data.menu_id);
- return $.Deferred().resolve();
+ return Promise.resolve();
},
on_menu_clicked: function (ev) {
var self = this;
return this.menu_dm.add(data_manager.load_action(ev.data.action_id))
.then(function (result) {
- return self.action_mutex.exec(function () {
- var completed = $.Deferred();
- $.when(self._openMenu(result, {
- clear_breadcrumbs: true,
- })).always(function () {
- completed.resolve();
- });
+ self.$el.removeClass('o_mobile_menu_opened');
- setTimeout(function () {
- completed.resolve();
- }, 2000);
+ return self.action_mutex.exec(function () {
+ var completed = new Promise(function (resolve, reject) {
+ Promise.resolve(self._openMenu(result, {
+ clear_breadcrumbs: true,
+ })).then(resolve).guardedCatch(reject);
+ setTimeout(function () {
+ resolve();
+ }, 2000);
+ });
return completed;
});
- }).always(function () {
+ }).guardedCatch(function () {
self.$el.removeClass('o_mobile_menu_opened');
});
},
@@ -233,7 +236,7 @@ return AbstractWebClient.extend({
* @private
* @param {Object} action
* @param {Object} options
- * @returns {Deferred}
+ * @returns {Promise}
*/
_openMenu: function (action, options) {
return this.do_action(action, options);
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index 509126498bc..8e7216c7b00 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -3,22 +3,19 @@ odoo.define('web.ajax', function (require) {
var config = require('web.config');
var core = require('web.core');
-var utils = require('web.utils');
var time = require('web.time');
var download = require('web.download');
var contentdisposition = require('web.contentdisposition');
var _t = core._t;
-function genericJsonRpc (fct_name, params, settings, fct) {
+function _genericJsonRpc (fct_name, params, settings, fct) {
var shadow = settings.shadow || false;
delete settings.shadow;
if (!shadow) {
core.bus.trigger('rpc_request');
}
- var deferred = $.Deferred();
-
var data = {
jsonrpc: "2.0",
method: fct_name,
@@ -26,7 +23,7 @@ function genericJsonRpc (fct_name, params, settings, fct) {
id: Math.floor(Math.random() * 1000 * 1000 * 1000)
};
var xhr = fct(data);
- var result = xhr.pipe(function(result) {
+ var result = xhr.then(function(result) {
core.bus.trigger('rpc:result', data, result);
if (result.error !== undefined) {
if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") {
@@ -36,65 +33,86 @@ function genericJsonRpc (fct_name, params, settings, fct) {
console.error("Server application error", JSON.stringify(result.error));
}
}
- return $.Deferred().reject("server", result.error);
+ return Promise.reject({type: "server", error: result.error});
} else {
return result.result;
}
}, function() {
//console.error("JsonRPC communication error", _.toArray(arguments));
- var def = $.Deferred();
- return def.reject.apply(def, ["communication"].concat(_.toArray(arguments)));
+ var reason = {
+ type: 'communication',
+ error: arguments[0],
+ textStatus: arguments[1],
+ errorThrown: arguments[2],
+ };
+ return Promise.reject(reason);
});
- deferred.abort = function () {
- deferred.reject({message: "XmlHttpRequestError abort"}, $.Event('abort'));
- if (xhr.abort) {
- xhr.abort();
- }
- };
- result.then(function (result) {
- if (!shadow) {
- core.bus.trigger('rpc_response');
- }
- deferred.resolve(result);
- }, function (type, error, textStatus, errorThrown) {
- if (type === "server") {
+ var rejection;
+ var promise = new Promise(function (resolve, reject) {
+ rejection = reject;
+
+ result.then(function (result) {
if (!shadow) {
core.bus.trigger('rpc_response');
}
- if (error.code === 100) {
- core.bus.trigger('invalidate_session');
- }
- deferred.reject(error, $.Event());
- } else {
- if (!shadow) {
- core.bus.trigger('rpc_response_failed');
+ resolve(result);
+ }, function (reason) {
+ var type = reason.type;
+ var error = reason.error;
+ var textStatus = reason.textStatus;
+ var errorThrown = reason.errorThrown;
+ if (type === "server") {
+ if (!shadow) {
+ core.bus.trigger('rpc_response');
+ }
+ if (error.code === 100) {
+ core.bus.trigger('invalidate_session');
+ }
+ reject({message: error, event: $.Event()});
+ } else {
+ if (!shadow) {
+ core.bus.trigger('rpc_response_failed');
+ }
+ var nerror = {
+ code: -32098,
+ message: "XmlHttpRequestError " + errorThrown,
+ data: {
+ type: "xhr"+textStatus,
+ debug: error.responseText,
+ objects: [error, errorThrown]
+ },
+ };
+ reject({message: nerror, event: $.Event()});
}
- var nerror = {
- code: -32098,
- message: "XmlHttpRequestError " + errorThrown,
- data: {
- type: "xhr"+textStatus,
- debug: error.responseText,
- objects: [error, errorThrown]
- },
- };
- deferred.reject(nerror, $.Event());
- }
+ });
});
- deferred.fail(function () { // Allow deferred user to disable rpc_error call in fail
- deferred.fail(function (error, event) {
- if (!event.isDefaultPrevented()) {
- core.bus.trigger('rpc_error', error, event);
- }
+
+ // FIXME: jsonp?
+ promise.abort = function () {
+ rejection({
+ message: "XmlHttpRequestError abort",
+ event: $.Event('abort')
});
+ if (xhr.abort) {
+ xhr.abort();
+ }
+ };
+ promise.guardedCatch(function (reason) { // Allow promise user to disable rpc_error call in case of failure
+ setTimeout(function () {
+ // we want to execute this handler after all others (hence
+ // setTimeout) to let the other handlers prevent the event
+ if (!reason.event.isDefaultPrevented()) {
+ core.bus.trigger('rpc_error', reason.message, reason.event);
+ }
+ }, 0);
});
- return deferred;
-}
+ return promise;
+};
function jsonRpc(url, fct_name, params, settings) {
settings = settings || {};
- return genericJsonRpc(fct_name, params, settings, function(data) {
+ return _genericJsonRpc(fct_name, params, settings, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'json',
@@ -110,19 +128,6 @@ function rpc(url, params, settings) {
return jsonRpc(url, 'call', params, settings);
}
-// helper
-function realSetTimeout (fct, millis) {
- var finished = new Date().getTime() + millis;
- var wait = function() {
- var current = new Date().getTime();
- if (current < finished) {
- setTimeout(wait, finished - current);
- } else {
- fct();
- }
- };
- setTimeout(wait, millis);
-}
/**
* Load css asynchronously: fetch it from the url parameter and add a link tag
@@ -131,26 +136,27 @@ function realSetTimeout (fct, millis) {
* immediately.
*
* @param {String} url of the css to be fetched
- * @returns {Deferred} resolved when the css has been loaded.
+ * @returns {Promise} resolved when the css has been loaded.
*/
var loadCSS = (function () {
- var urlDefs = Object.create(null);
+ var urlDefs = {};
return function loadCSS(url) {
if (url in urlDefs) {
// nothing to do here
} else if ($('link[href="' + url + '"]').length) {
- // the link is already in the DOM, the deferred can be resolved
- urlDefs[url] = $.when();
+ // the link is already in the DOM, the promise can be resolved
+ urlDefs[url] = Promise.resolve();
} else {
var $link = $('<link>', {
'href': url,
'rel': 'stylesheet',
'type': 'text/css'
});
- urlDefs[url] = $.Deferred();
- $link.on('load', function () {
- urlDefs[url].resolve();
+ urlDefs[url] = new Promise(function (resolve, reject) {
+ $link.on('load', function () {
+ resolve();
+ });
});
$('head').append($link);
}
@@ -159,44 +165,45 @@ var loadCSS = (function () {
})();
var loadJS = (function () {
- var urls = [];
- var defs = [];
+ var dependenciesPromise = {};
var load = function loadJS(url) {
// Check the DOM to see if a script with the specified url is already there
var alreadyRequired = ($('script[src="' + url + '"]').length > 0);
- // If loadJS was already called with the same URL, it will have a registered deferred indicating if
- // the script has been fully loaded. If not, the deferred has to be initialized. This is initialized
- // as already resolved if the script was already there without the need of loadJS.
- var index = _.indexOf(urls, url);
- if (index < 0) {
- urls.push(url);
- index = defs.push(alreadyRequired ? $.when() : $.Deferred()) - 1;
+ // If loadJS was already called with the same URL, it will have a registered promise indicating if
+ // the script has been fully loaded. If not, the promise has to be initialized.
+ // This is initialized as already resolved if the script was already there without the need of loadJS.
+ if (url in dependenciesPromise) {
+ return dependenciesPromise[url];
}
+ var scriptLoadedPromise = new Promise(function (resolve, reject) {
+ if (alreadyRequired) {
+ resolve();
+ } else {
+ // Get the script associated promise and returns it after initializing the script if needed. The
+ // promise is marked to be resolved on script load and rejected on script error.
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+ script.onload = script.onreadystatechange = function() {
+ if ((script.readyState && script.readyState !== "loaded" && script.readyState !== "complete") || script.onload_done) {
+ return;
+ }
+ script.onload_done = true;
+ resolve(url);
+ };
+ script.onerror = function () {
+ console.error("Error loading file", script.src);
+ reject(url);
+ };
+ var head = document.head || document.getElementsByTagName('head')[0];
+ head.appendChild(script);
+ }
+ });
- // Get the script associated deferred and returns it after initializing the script if needed. The
- // deferred is marked to be resolved on script load and rejected on script error.
- var def = defs[index];
- if (!alreadyRequired) {
- var script = document.createElement('script');
- script.type = 'text/javascript';
- script.src = url;
- script.onload = script.onreadystatechange = function() {
- if ((script.readyState && script.readyState !== "loaded" && script.readyState !== "complete") || script.onload_done) {
- return;
- }
- script.onload_done = true;
- def.resolve(url);
- };
- script.onerror = function () {
- console.error("Error loading file", script.src);
- def.reject(url);
- };
- var head = document.head || document.getElementsByTagName('head')[0];
- head.appendChild(script);
- }
- return def;
+ dependenciesPromise[url] = scriptLoadedPromise;
+ return scriptLoadedPromise;
};
return load;
@@ -308,22 +315,6 @@ function get_file(options) {
}
function post (controller_url, data) {
-
- var progressHandler = function (deferred) {
- return function (state) {
- if(state.lengthComputable) {
- deferred.notify({
- h_loaded: utils.human_size(state.loaded),
- h_total : utils.human_size(state.total),
- loaded : state.loaded,
- total : state.total,
- pcent : Math.round((state.loaded/state.total)*100)
- });
- }
- };
- };
-
- var Def = $.Deferred();
var postData = new FormData();
$.each(data, function(i,val) {
@@ -333,25 +324,20 @@ function post (controller_url, data) {
postData.append('csrf_token', core.csrf_token);
}
- var xhr = new XMLHttpRequest();
- if(xhr.upload) xhr.upload.addEventListener('progress', progressHandler(Def), false);
-
- var ajaxDef = $.ajax(controller_url, {
- xhr: function() {return xhr;},
- data: postData,
- processData: false,
- contentType: false,
- type: 'POST'
- }).then(function (data) {Def.resolve(data);})
- .fail(function (data) {Def.reject(data);});
-
- return Def;
+ return new Promise(function (resolve, reject) {
+ $.ajax(controller_url, {
+ data: postData,
+ processData: false,
+ contentType: false,
+ type: 'POST'
+ }).then(resolve).fail(reject);
+ });
}
/**
* Loads an XML file according to the given URL and adds its associated qweb
* templates to the given qweb engine. The function can also be used to get
- * the deferred which indicates when all the calls to the function are finished.
+ * the promise which indicates when all the calls to the function are finished.
*
* Note: "all the calls" = the calls that happened before the current no-args
* one + the calls that will happen after but when the previous ones are not
@@ -359,8 +345,8 @@ function post (controller_url, data) {
*
* @param {string} [url] - an URL where to find qweb templates
* @param {QWeb} [qweb] - the engine to which the templates need to be added
- * @returns {Deferred}
- * If no argument is given to the function, the deferred's state
+ * @returns {Promise}
+ * If no argument is given to the function, the promise's state
* indicates if "all the calls" are finished (see main description).
* Otherwise, it indicates when the templates associated to the given
* url have been loaded.
@@ -369,63 +355,64 @@ var loadXML = (function () {
// Some "static" variables associated to the loadXML function
var isLoading = false;
var loadingsData = [];
- var allLoadingsDef = $.when();
var seenURLs = [];
return function (url, qweb) {
- // If no argument, simply returns the deferred which indicates when
+ function _load() {
+ isLoading = true;
+ if (loadingsData.length) {
+ // There is something to load, load it, resolve the associated
+ // promise then start loading the next one
+ var loadingData = loadingsData[0];
+ loadingData.qweb.add_template(loadingData.url, function () {
+ // Remove from array only now so that multiple calls to
+ // loadXML with the same URL returns the right promise
+ loadingsData.shift();
+ loadingData.resolve();
+ _load();
+ });
+ } else {
+ // There is nothing to load anymore, so resolve the
+ // "all the calls" promise
+ isLoading = false;
+ }
+ }
+
+ // If no argument, simply returns the promise which indicates when
// "all the calls" are finished
if (!url || !qweb) {
- return allLoadingsDef;
+ return Promise.resolve();
}
// If the given URL has already been seen, do nothing but returning the
- // associated deferred
+ // associated promise
if (_.contains(seenURLs, url)) {
var oldLoadingData = _.findWhere(loadingsData, {url: url});
- return oldLoadingData ? oldLoadingData.def : $.when();
+ return oldLoadingData ? oldLoadingData.def : Promise.resolve();
}
seenURLs.push(url);
+
// Add the information about the new data to load: the url, the qweb
- // engine and the associated deferred
+ // engine and the associated promise
var newLoadingData = {
url: url,
qweb: qweb,
- def: $.Deferred(),
};
+ newLoadingData.def = new Promise(function (resolve, reject) {
+ newLoadingData.resolve = resolve;
+ newLoadingData.reject = reject;
+ });
loadingsData.push(newLoadingData);
// If not already started, start the loading loop (reinitialize the
- // "all the calls" deferred to an unresolved state)
+ // "all the calls" promise to an unresolved state)
if (!isLoading) {
- allLoadingsDef = $.Deferred();
_load();
}
- // Return the deferred associated to the new given URL
+ // Return the promise associated to the new given URL
return newLoadingData.def;
-
- function _load() {
- isLoading = true;
- if (loadingsData.length) {
- // There is something to load, load it, resolve the associated
- // deferred then start loading the next one
- var loadingData = loadingsData[0];
- loadingData.qweb.add_template(loadingData.url, function () {
- // Remove from array only now so that multiple calls to
- // loadXML with the same URL returns the right deferred
- loadingsData.shift();
- loadingData.def.resolve();
- _load();
- });
- } else {
- // There is nothing to load anymore, so resolve the
- // "all the calls" deferred
- isLoading = false;
- allLoadingsDef.resolve();
- }
- }
};
})();
@@ -444,7 +431,7 @@ var loadAsset = (function () {
var load = function loadAsset(xmlId) {
if (cache[xmlId]) {
- return $.when(cache[xmlId]);
+ return Promise.resolve(cache[xmlId]);
}
var params = {
args: [xmlId, {
@@ -493,22 +480,20 @@ var loadAsset = (function () {
* @param {Array<string>} [libs.assetLibs=[]] A list of xmlId. The loaded template
* contains the script and link to be loaded
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
function loadLibs (libs) {
var defs = [];
- _.each(libs.jsLibs || [], function (urls) {
- defs.push($.when.apply($, defs).then(function () {
+ (libs.jsLibs || []).forEach(function (urls) {
+ defs.push(Promise.all(defs).then(function () {
if (typeof(urls) === 'string') {
return ajax.loadJS(urls);
} else {
- return $.when.apply($, _.map(urls, function (url) {
- return ajax.loadJS(url);
- }));
+ return Promise.all(urls.map(ajax.loadJS));
}
}));
});
- _.each(libs.cssLibs || [], function (url) {
+ (libs.cssLibs || []).forEach(function (url) {
defs.push(ajax.loadCSS(url));
});
_.each(libs.assetLibs || [], function (xmlId) {
@@ -516,7 +501,7 @@ function loadLibs (libs) {
return loadLibs(asset);
}));
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
}
var ajax = {
diff --git a/addons/web/static/src/js/core/concurrency.js b/addons/web/static/src/js/core/concurrency.js
index 6eeebe7ff58..716abafa919 100644
--- a/addons/web/static/src/js/core/concurrency.js
+++ b/addons/web/static/src/js/core/concurrency.js
@@ -8,7 +8,7 @@ odoo.define('web.concurrency', function (require) {
* everything concurrency related in Odoo.
*
* The basic concurrency primitives in Odoo JS are the callback, and the
- * promises. Promises (deferred) are more composable, so we usually use them
+ * promises. Promises (promise) are more composable, so we usually use them
* whenever possible. We use the jQuery implementation.
*
* Those functions are really nothing special, but are simply the result of how
@@ -19,54 +19,15 @@ var Class = require('web.Class');
return {
/**
- * The jquery implementation for $.when has a (most of the time) useful
- * property: it is synchronous, if the deferred is resolved immediately.
- *
- * This means that when we execute $.when(def), then all registered
- * callbacks will be executed before the next line is executed. This is
- * useful quite often, but in some rare cases, we might want to force an
- * async behavior. This is the purpose of this function, which simply adds a
- * setTimeout before resolving the deferred.
- *
- * @returns {Deferred}
- */
- asyncWhen: function () {
- var async = false;
- var def = $.Deferred();
- $.when.apply($, arguments).done(function() {
- var args = arguments;
- var action = function() {
- def.resolve.apply(def, args);
- };
- if (async) {
- action();
- } else {
- setTimeout(action, 0);
- }
- }).fail(function() {
- var args = arguments;
- var action = function() {
- def.reject.apply(def, args);
- };
- if (async) {
- action();
- } else {
- setTimeout(action, 0);
- }
- });
- async = true;
- return def;
- },
- /**
- * Returns a deferred resolved after 'wait' milliseconds
+ * Returns a promise resolved after 'wait' milliseconds
*
* @param {int} [wait=0] the delay in ms
- * @return {Deferred}
+ * @return {Promise}
*/
delay: function (wait) {
- var def = $.Deferred();
- setTimeout(def.resolve, wait);
- return def;
+ return new Promise(function (resolve) {
+ setTimeout(resolve, wait);
+ });
},
/**
* The DropMisordered abstraction is useful for situations where you have
@@ -93,27 +54,27 @@ return {
this.failMisordered = failMisordered || false;
},
/**
- * Adds a deferred (usually an async request) to the sequencer
+ * Adds a promise (usually an async request) to the sequencer
*
- * @param {Deferred} deferred to ensure add
- * @returns {Deferred}
+ * @param {Promise} promise to ensure add
+ * @returns {Promise}
*/
- add: function (deferred) {
- var res = $.Deferred();
-
- var self = this, seq = this.lsn++;
- deferred.done(function () {
- if (seq > self.rsn) {
- self.rsn = seq;
- res.resolve.apply(res, arguments);
- } else if (self.failMisordered) {
- res.reject();
- }
- }).fail(function () {
- res.reject.apply(res, arguments);
+ add: function (promise) {
+ var self = this;
+ var seq = this.lsn++;
+ var res = new Promise(function (resolve, reject) {
+ promise.then(function (result) {
+ if (seq > self.rsn) {
+ self.rsn = seq;
+ resolve(result);
+ } else if (self.failMisordered) {
+ reject();
+ }
+ }).guardedCatch(function (result) {
+ reject(result);
+ });
});
-
- return res.promise();
+ return res;
},
}),
/**
@@ -152,17 +113,26 @@ return {
*/
DropPrevious: Class.extend({
/**
- * Registers a new deferred and rejects the previous one
+ * Registers a new promise and rejects the previous one
*
- * @param {Deferred} deferred the new deferred
+ * @param {Promise} promise the new promise
* @returns {Promise}
*/
- add: function (deferred) {
- if (this.current_def) { this.current_def.reject(); }
- var res = $.Deferred();
- deferred.then(res.resolve, res.reject);
- this.current_def = res;
- return res.promise();
+ add: function (promise) {
+ if (this.currentDef) {
+ this.currentDef.reject();
+ }
+ var rejection;
+ var res = new Promise(function (resolve, reject) {
+ rejection = reject;
+ promise.then(resolve).catch(function (reason) {
+ reject(reason);
+ });
+ });
+
+ this.currentDef = res;
+ this.currentDef.reject = rejection;
+ return res;
}
}),
/**
@@ -171,7 +141,7 @@ return {
* state and cause some corrupted state.
*
* Imagine that we have a function to fetch some data _load(), which returns
- * a deferred which resolves to something useful. Now, we have some code
+ * a promise which resolves to something useful. Now, we have some code
* looking like this::
*
* return this._load().then(function (result) {
@@ -200,37 +170,51 @@ return {
*/
Mutex: Class.extend({
init: function () {
- this.def = $.Deferred().resolve();
- this.unlockedDef = undefined;
+ this.lock = Promise.resolve();
+ this.queueSize = 0;
+ this.unlockedProm = undefined;
+ this._unlock = undefined;
},
/**
* Add a computation to the queue, it will be executed as soon as the
* previous computations are completed.
*
- * @param {function} action a function which may return a deferred
- * @returns {Deferred}
+ * @param {function} action a function which may return a Promise
+ * @returns {Promise}
*/
exec: function (action) {
var self = this;
- var current = this.def;
- var next = this.def = $.Deferred();
- this.unlockedDef = this.unlockedDef || $.Deferred();
- return current.then(function() {
- return $.when(action()).always(function () {
- next.resolve();
- if (self.def.state() === 'resolved' && self.unlockedDef) {
- self.unlockedDef.resolve();
- self.unlockedDef = undefined;
- }
+ var currentLock = this.lock;
+ var result;
+ this.queueSize++;
+ this.unlockedProm = this.unlockedProm || new Promise(function (resolve) {
+ self._unlock = resolve;
+ });
+ this.lock = new Promise(function (unlockCurrent) {
+ currentLock.then(function () {
+ result = action();
+ var always = function (returnedResult) {
+ unlockCurrent();
+ self.queueSize--;
+ if (self.queueSize === 0) {
+ self.unlockedProm = undefined;
+ self._unlock();
+ }
+ return returnedResult;
+ };
+ Promise.resolve(result).then(always).guardedCatch(always);
});
});
+ return this.lock.then(function () {
+ return result;
+ });
},
/**
* @returns {Promise} resolved as soon as the Mutex is unlocked
* (directly if it is currently idle)
*/
getUnlockedDef: function () {
- return $.when(this.unlockedDef);
+ return this.unlockedProm || Promise.resolve();
},
}),
/**
@@ -253,65 +237,86 @@ return {
*
* A MutexedDropPrevious has to be a class, and not a function, because we
* have to keep track of some internal state. The exec function takes as
- * argument an action (and not a deferred as DropPrevious for example)
+ * argument an action (and not a promise as DropPrevious for example)
* because it's the MutexedDropPrevious role to trigger the RPC call that
- * returns a deferred when it's time.
+ * returns a promise when it's time.
*/
MutexedDropPrevious: Class.extend({
init: function () {
- this.currentDef = null;
this.locked = false;
+ this.currentProm = null;
this.pendingAction = null;
- this.pendingDef = null;
+ this.pendingProm = null;
},
/**
- * @param {function} action a function which may return a deferred
- * @returns {Deferred}
+ * @param {function} action a function which may return a promise
+ * @returns {Promise}
*/
exec: function (action) {
var self = this;
+ var resolution;
+ var rejection;
if (this.locked) {
this.pendingAction = action;
- var oldPendingDef = this.pendingDef;
- var pendingDef = this.pendingDef = $.Deferred();
- if (oldPendingDef) {
- oldPendingDef.reject();
- }
- this.currentDef.reject();
- return pendingDef.promise();
+ var oldPendingDef = this.pendingProm;
+
+ this.pendingProm = new Promise(function (resolve, reject) {
+ resolution = resolve;
+ rejection = reject;
+ if (oldPendingDef) {
+ oldPendingDef.reject();
+ }
+ self.currentProm.reject();
+ });
+ this.pendingProm.resolve = resolution;
+ this.pendingProm.reject = rejection;
+ return this.pendingProm;
} else {
this.locked = true;
- this.currentDef = $.Deferred();
- $.when(action())
- .then(this.currentDef.resolve.bind(this.currentDef))
- .fail(this.currentDef.reject.bind(this.currentDef))
- .always(function () {
+ this.currentProm = new Promise(function (resolve, reject) {
+ resolution = resolve;
+ rejection = reject;
+ function unlock() {
self.locked = false;
if (self.pendingAction) {
var action = self.pendingAction;
+ var prom = self.pendingProm;
self.pendingAction = null;
+ self.pendingProm = null;
self.exec(action)
- .then(self.pendingDef.resolve.bind(self.pendingDef))
- .fail(self.pendingDef.reject.bind(self.pendingDef));
+ .then(prom.resolve)
+ .guardedCatch(prom.reject);
}
+ }
+ Promise.resolve(action())
+ .then(function (result) {
+ resolve(result);
+ unlock();
+ })
+ .guardedCatch(function (reason) {
+ reject(reason);
+ unlock();
+ });
});
- return this.currentDef.promise();
+ this.currentProm.resolve = resolution;
+ this.currentProm.reject = rejection;
+ return this.currentProm;
}
- },
+ }
}),
/**
- * Rejects a deferred as soon as a reference deferred is either resolved or
+ * Rejects a promise as soon as a reference promise is either resolved or
* rejected
*
- * @param {Deferred} [target_def] the deferred to potentially reject
- * @param {Deferred} [reference_def] the reference target
- * @returns {Deferred}
+ * @param {Promise} [target_def] the promise to potentially reject
+ * @param {Promise} [reference_def] the reference target
+ * @returns {Promise}
*/
rejectAfter: function (target_def, reference_def) {
- var res = $.Deferred();
- target_def.then(res.resolve, res.reject);
- reference_def.always(res.reject);
- return res.promise();
+ return new Promise(function (resolve, reject) {
+ target_def.then(resolve).guardedCatch(reject);
+ reference_def.then(reject).guardedCatch(reject);
+ });
}
};
diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js
index 83eded92c8a..8630b6b9364 100644
--- a/addons/web/static/src/js/core/dialog.js
+++ b/addons/web/static/src/js/core/dialog.js
@@ -52,9 +52,11 @@ var Dialog = Widget.extend({
* (use this for non-editor frontend features)
*/
init: function (parent, options) {
+ var self = this;
this._super(parent);
- this._opened = $.Deferred();
-
+ this._opened = new Promise(function (resolve) {
+ self._openedResolver = resolve;
+ });
options = _.defaults(options || {}, {
title: _t('Odoo'), subtitle: '',
size: 'large',
@@ -140,7 +142,7 @@ var Dialog = Widget.extend({
def = buttonData.click.call(self, e);
}
if (buttonData.close) {
- $.when(def).always(self.close.bind(self));
+ Promise.resolve(def).then(self.close.bind(self)).guardedCatch(self.close.bind(self));
}
});
if (self.technical) {
@@ -185,7 +187,7 @@ var Dialog = Widget.extend({
self.$modal.attr('open', true);
self.$modal.removeAttr("aria-hidden");
self.$modal.modal('show');
- self._opened.resolve();
+ self._openedResolver();
if (options && options.shouldFocusButtons) {
self._onFocusControlButton();
}
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index b8a5b8be3a4..3faa5977c8c 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -1,11 +1,10 @@
odoo.define('web.dom_ready', function (require) {
'use strict';
-var def = $.Deferred();
-$(def.resolve.bind(def));
-return def;
+ return new Promise(function (resolve, reject) {
+ $(resolve);
+ });
});
-
//==============================================================================
odoo.define('web.dom', function (require) {
@@ -33,7 +32,7 @@ var _t = core._t;
* that on_attach_callback() will be called on each w with arguments args
*/
function _notify(content, callbacks) {
- _.each(callbacks, function (c) {
+ callbacks.forEach(function (c) {
if (c.widget && c.widget.on_attach_callback) {
c.widget.on_attach_callback(c.callback_args);
}
@@ -170,14 +169,14 @@ var dom = {
* @return {jQuery} the detached elements
*/
detach: function (to_detach, options) {
- _.each(to_detach, function (d) {
+ to_detach.forEach( function (d) {
if (d.widget.on_detach_callback) {
d.widget.on_detach_callback(d.callback_args);
}
});
var $to_detach = options && options.$to_detach;
if (!$to_detach) {
- $to_detach = $(_.map(to_detach, function (d) {
+ $to_detach = $(to_detach.map(function (d) {
return d.widget.el;
}));
}
@@ -229,7 +228,7 @@ var dom = {
* @param {function|boolean} stopPropagation
*/
makeAsyncHandler: function (fct, preventDefault, stopPropagation) {
- // Create a deferred indicating if a previous call to this handler is
+ // Create a promise indicating if a previous call to this handler is
// still pending.
var def = $.when();
@@ -284,11 +283,13 @@ var dom = {
// a 'real' debounce creation useless. Also, during the debouncing
// part, the button is disabled without any visual effect.
$button.addClass('o_debounce_disabled');
- $.when(dom.DEBOUNCE && concurrency.delay(dom.DEBOUNCE)).then(function () {
+ Promise.resolve(dom.DEBOUNCE && concurrency.delay(dom.DEBOUNCE)).then(function () {
$button.addClass('disabled').prop('disabled', true);
$button.removeClass('o_debounce_disabled');
- return $.when(result).always(function () {
+ return Promise.resolve(result).then(function () {
+ $button.removeClass('disabled').prop('disabled', false);
+ }).guardedCatch(function () {
$button.removeClass('disabled').prop('disabled', false);
});
});
diff --git a/addons/web/static/src/js/core/misc.js b/addons/web/static/src/js/core/misc.js
index cd9a73ef22b..39fd8021071 100644
--- a/addons/web/static/src/js/core/misc.js
+++ b/addons/web/static/src/js/core/misc.js
@@ -114,7 +114,7 @@ function redirect (url, wait) {
};
var wait_server = function() {
- ajax.rpc("/web/webclient/version_info", {}).done(load).fail(function() {
+ ajax.rpc("/web/webclient/version_info", {}).then(load).guardedCatch(function () {
setTimeout(wait_server, 250);
});
};
@@ -172,7 +172,7 @@ core.action_registry.add("login", login);
function logout() {
redirect('/web/session/logout');
- return $.Deferred();
+ return new Promise();
}
core.action_registry.add("logout", logout);
diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js
index e29c97ea086..228ac2d68dd 100644
--- a/addons/web/static/src/js/core/mixins.js
+++ b/addons/web/static/src/js/core/mixins.js
@@ -62,37 +62,36 @@ var ParentedMixin = {
* Utility method to only execute asynchronous actions if the current
* object has not been destroyed.
*
- * @param {$.Deferred} promise The promise representing the asynchronous
+ * @param {Promise} promise The promise representing the asynchronous
* action.
- * @param {bool} [reject=false] If true, the returned promise will be
+ * @param {bool} [shouldReject=false] If true, the returned promise will be
* rejected with no arguments if the current
* object is destroyed. If false, the
* returned promise will never be resolved
* or rejected.
- * @returns {$.Deferred} A promise that will mirror the given promise if
+ * @returns {Promise} A promise that will mirror the given promise if
* everything goes fine but will either be rejected
* with no arguments or never resolved if the
* current object is destroyed.
*/
- alive: function (promise, reject) {
+ alive: function (promise, shouldReject) {
var self = this;
- return $.Deferred(function (def) {
- promise.then(function () {
+
+ return new Promise(function (resolve, reject) {
+ promise.then(function (result) {
if (!self.isDestroyed()) {
- def.resolve.apply(def, arguments);
+ resolve(result);
+ } else if (shouldReject) {
+ reject();
}
- }, function () {
+ }).guardedCatch(function (reason) {
if (!self.isDestroyed()) {
- def.reject.apply(def, arguments);
- }
- }).always(function () {
- if (reject) {
- // noop if def already resolved or rejected
- def.reject();
+ reject(reason);
+ } else if (shouldReject) {
+ reject();
}
- // otherwise leave promise in limbo
});
- }).promise();
+ });
},
/**
* Inform the object it should destroy itself, releasing any
diff --git a/addons/web/static/src/js/core/mvc.js b/addons/web/static/src/js/core/mvc.js
index cdef88c4211..6e0e4267caa 100644
--- a/addons/web/static/src/js/core/mvc.js
+++ b/addons/web/static/src/js/core/mvc.js
@@ -76,7 +76,7 @@ var Model = Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
* @returns {Promise} The promise resolves to some kind of handle
*/
load: function () {
- return $.when();
+ return Promise.resolve();
},
});
@@ -119,9 +119,9 @@ var Controller = Widget.extend({
* @returns {Promise}
*/
start: function () {
- return $.when(
- this._super.apply(this, arguments),
- this._startRenderer()
+ return Promise.all(
+ [this._super.apply(this, arguments),
+ this._startRenderer()]
);
},
@@ -177,7 +177,8 @@ var Factory = Class.extend({
getController: function (parent) {
var self = this;
var model = this.getModel(parent);
- return $.when(this._loadData(model), ajax.loadLibs(this)).then(function (state) {
+ return Promise.all([this._loadData(model), ajax.loadLibs(this)]).then(function (result) {
+ var state = result[0];
var renderer = self.getRenderer(parent, state);
var Controller = self.Controller || self.config.Controller;
var controllerParams = _.extend({
diff --git a/addons/web/static/src/js/core/rpc.js b/addons/web/static/src/js/core/rpc.js
index cc56cb74ebb..21ec43e3d95 100644
--- a/addons/web/static/src/js/core/rpc.js
+++ b/addons/web/static/src/js/core/rpc.js
@@ -11,7 +11,7 @@ return {
*
* @param {Object} params @see buildQuery for a description
* @param {Object} options
- * @returns {Deferred<any>}
+ * @returns {Promise<any>}
*/
query: function (params, options) {
var query = this.buildQuery(params);
diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js
index e8088d45cb5..4f296b6bb90 100644
--- a/addons/web/static/src/js/core/service_mixins.js
+++ b/addons/web/static/src/js/core/service_mixins.js
@@ -124,7 +124,7 @@ var ServicesMixin = {
return result;
},
/**
- * Builds and executes RPC query. Returns a deferred's promise resolved with
+ * Builds and executes RPC query. Returns a promise resolved with
* the RPC result.
*
* @param {string} params either a route or a model
@@ -133,11 +133,17 @@ var ServicesMixin = {
*/
_rpc: function (params, options) {
var query = rpc.buildQuery(params);
- var def = this.call('ajax', 'rpc', query.route, query.params, options, this) || $.Deferred();
- var promise = def.promise();
- var abort = (def.abort ? def.abort : def.reject) || function () {};
- promise.abort = abort.bind(def);
- return promise;
+ var prom = this.call('ajax', 'rpc', query.route, query.params, options, this);
+ if (!prom) {
+ prom = new Promise(function () {});
+ prom.abort = function () {};
+ }
+ var abort = prom.abort ? prom.abort : prom.reject;
+ if (!abort) {
+ throw new Error("a rpc promise should always have a reject function");
+ }
+ prom.abort = abort.bind(prom);
+ return prom;
},
loadFieldView: function (modelName, context, view_id, view_type, options) {
return this.loadViews(modelName, context, [[view_id, view_type]], options).then(function (result) {
@@ -145,41 +151,45 @@ var ServicesMixin = {
});
},
loadViews: function (modelName, context, views, options) {
- var def = $.Deferred();
- this.trigger_up('load_views', {
- modelName: modelName,
- context: context,
- views: views,
- options: options,
- on_success: def.resolve.bind(def),
+ var self = this;
+ return new Promise(function (resolve) {
+ self.trigger_up('load_views', {
+ modelName: modelName,
+ context: context,
+ views: views,
+ options: options,
+ on_success: resolve,
+ });
});
- return def;
},
loadFilters: function (modelName, actionId, context) {
- var def = $.Deferred();
- this.trigger_up('load_filters', {
- modelName: modelName,
- actionId: actionId,
- context: context,
- on_success: def.resolve.bind(def),
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ self.trigger_up('load_filters', {
+ modelName: modelName,
+ actionId: actionId,
+ context: context,
+ on_success: resolve,
+ });
});
- return def;
},
createFilter: function (filter) {
- var def = $.Deferred();
- this.trigger_up('create_filter', {
- filter: filter,
- on_success: def.resolve.bind(def),
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ self.trigger_up('create_filter', {
+ filter: filter,
+ on_success: resolve,
+ });
});
- return def;
},
deleteFilter: function (filterId) {
- var def = $.Deferred();
- this.trigger_up('delete_filter', {
- filterId: filterId,
- on_success: def.resolve.bind(def),
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ self.trigger_up('delete_filter', {
+ filterId: filterId,
+ on_success: resolve,
+ });
});
- return def;
},
// Session stuff
getSession: function () {
@@ -195,22 +205,22 @@ var ServicesMixin = {
* Informs the action manager to do an action. This supposes that the action
* manager can be found amongst the ancestors of the current widget.
* If that's not the case this method will simply return an unresolved
- * deferred.
+ * promise.
*
* @param {any} action
* @param {any} options
- * @returns {Deferred}
+ * @returns {Promise}
*/
do_action: function (action, options) {
- var def = $.Deferred();
-
- this.trigger_up('do_action', {
- action: action,
- options: options,
- on_success: function (result) { def.resolve(result); },
- on_fail: function (result) { def.reject(result); },
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ self.trigger_up('do_action', {
+ action: action,
+ options: options,
+ on_success: resolve,
+ on_fail: reject,
+ });
});
- return def;
},
do_notify: function (title, message, sticky, className) {
return this.call('notification', 'notify', {title: title, message: message, sticky: sticky, className: className});
diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js
index 14886591d7b..2874a2ea559 100644
--- a/addons/web/static/src/js/core/session.js
+++ b/addons/web/static/src/js/core/session.js
@@ -84,27 +84,27 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
*/
session_init: function () {
var self = this;
- var def = this.session_reload();
+ var prom = this.session_reload();
if (this.is_frontend) {
- return def.then(function () {
+ return prom.then(function () {
return self.load_translations();
});
}
- return def.then(function () {
+ return prom.then(function () {
var modules = self.module_list.join(',');
- var deferred = self.load_qweb(modules);
+ var promise = self.load_qweb(modules);
if (self.session_is_valid()) {
- return deferred.then(function () { return self.load_modules(); });
+ return promise.then(function () { return self.load_modules(); });
}
- return $.when(
- deferred,
+ return Promise.all([
+ promise,
self.rpc('/web/webclient/bootstrap_translations', {mods: self.module_list})
.then(function (trans) {
_t.database.set_bundle(trans);
})
- );
+ ]);
});
},
session_is_valid: function () {
@@ -119,7 +119,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
*/
session_authenticate: function () {
var self = this;
- return $.when(this._session_authenticate.apply(this, arguments)).then(function () {
+ return Promise.resolve(this._session_authenticate.apply(this, arguments)).then(function () {
return self.load_modules();
});
},
@@ -131,7 +131,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
var params = {db: db, login: login, password: password};
return this.rpc("/web/session/authenticate", params).then(function (result) {
if (!result.uid) {
- return $.Deferred().reject();
+ return Promise.reject();
}
_.extend(self, result);
});
@@ -142,7 +142,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
},
user_has_group: function (group) {
if (!this.uid) {
- return $.when(false);
+ return Promise.resolve(false);
}
var def = this._groups_def[group];
if (!def) {
@@ -196,22 +196,24 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
var to_load = _.difference(modules, self.module_list).join(',');
this.module_list = all_modules;
- var loaded = $.when(self.load_translations());
+ var loaded = Promise.resolve(self.load_translations());
var locale = "/web/webclient/locale/" + self.user_context.lang || 'en_US';
var file_list = [ locale ];
if(to_load.length) {
- loaded = $.when(
+ loaded = Promise.all([
loaded,
- self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
+ self.rpc('/web/webclient/csslist', {mods: to_load})
+ .then(self.load_css.bind(self)),
self.load_qweb(to_load),
- self.rpc('/web/webclient/jslist', {mods: to_load}).done(function (files) {
- file_list = file_list.concat(files);
- })
- );
+ self.rpc('/web/webclient/jslist', {mods: to_load})
+ .then(function (files) {
+ file_list = file_list.concat(files);
+ })
+ ]);
}
return loaded.then(function () {
return self.load_js(file_list);
- }).done(function () {
+ }).then(function () {
self.on_modules_loaded();
self.trigger('module_loaded');
});
@@ -227,24 +229,24 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
},
load_js: function (files) {
var self = this;
- var d = $.Deferred();
- if (files.length !== 0) {
- var file = files.shift();
- var url = self.url(file, null);
- ajax.loadJS(url).done(d.resolve);
- } else {
- d.resolve();
- }
- return d;
+ return new Promise(function (resolve, reject) {
+ if (files.length !== 0) {
+ var file = files.shift();
+ var url = self.url(file, null);
+ ajax.loadJS(url).then(resolve);
+ } else {
+ resolve();
+ }
+ });
},
load_qweb: function (mods) {
- this.qweb_mutex.exec(function () {
+ var lock = this.qweb_mutex.exec(function () {
return $.get('/web/webclient/qweb?mods=' + mods).then(function (doc) {
if (!doc) { return; }
qweb.add_template(doc);
});
});
- return this.qweb_mutex.def;
+ return lock;
},
on_modules_loaded: function () {
var openerp = window.openerp;
@@ -276,12 +278,12 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
* (re)loads the content of a session: db name, username, user id, session
* context and status of the support contract
*
- * @returns {$.Deferred} deferred indicating the session is done reloading
+ * @returns {Promise} promise indicating the session is done reloading
*/
session_reload: function () {
var result = _.extend({}, window.odoo.session_info);
_.extend(this, result);
- return $.when();
+ return Promise.resolve();
},
/**
* Executes an RPC call, registering the provided callbacks.
@@ -293,7 +295,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
* @param {String} url RPC endpoint
* @param {Object} params call parameters
* @param {Object} options additional options for rpc call
- * @returns {jQuery.Deferred} jquery-provided ajax deferred
+ * @returns {Promise}
*/
rpc: function (url, params, options) {
var self = this;
diff --git a/addons/web/static/src/js/core/translation.js b/addons/web/static/src/js/core/translation.js
index 76493fdb432..83c556c3c37 100644
--- a/addons/web/static/src/js/core/translation.js
+++ b/addons/web/static/src/js/core/translation.js
@@ -58,14 +58,14 @@ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase#
@param {Object} [lang] lang The language. If not specified it will default to the language
of the current user.
@param {string} [url='/web/webclient/translations']
- @returns {jQuery.Deferred}
+ @returns {Promise}
*/
load_translations: function(session, modules, lang, url) {
var self = this;
return session.rpc(url || '/web/webclient/translations', {
"mods": modules || null,
"lang": lang || null
- }).done(function(trans) {
+ }).then(function (trans) {
self.set_bundle(trans);
});
}
diff --git a/addons/web/static/src/js/core/widget.js b/addons/web/static/src/js/core/widget.js
index d3b9cc1033c..0de8c6aa1cd 100644
--- a/addons/web/static/src/js/core/widget.js
+++ b/addons/web/static/src/js/core/widget.js
@@ -31,7 +31,7 @@ var ServicesMixin = require('web.ServicesMixin');
* },
* willStart: function () {
* // async work that need to be done before the widget is ready
- * // this method should return a deferred
+ * // this method should return a promise
* },
* start: function() {
* // stuff you want to make after the rendering, `this.$el` holds a correct value
@@ -135,45 +135,47 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* Method called between @see init and @see start. Performs asynchronous
* calls required by the rendering and the start method.
*
- * This method should return a Deferred which is resolved when start can be
+ * This method should return a Promose which is resolved when start can be
* executed.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
willStart: function () {
- var defs = [];
+ var proms = [];
if (this.xmlDependencies) {
- defs.push.apply(defs, _.map(this.xmlDependencies, function (xmlPath) {
+ proms.push.apply(proms, _.map(this.xmlDependencies, function (xmlPath) {
return ajax.loadXML(xmlPath, core.qweb);
}));
}
if (this.jsLibs || this.cssLibs || this.assetLibs) {
- defs.push(ajax.loadLibs(this));
+ proms.push(ajax.loadLibs(this));
}
- return $.when.apply($, defs);
+ return Promise.all(proms);
},
/**
* Method called after rendering. Mostly used to bind actions, perform
* asynchronous calls, etc...
*
* By convention, this method should return an object that can be passed to
- * $.when() to inform the caller when this widget has been initialized.
+ * Promise.resolve() to inform the caller when this widget has been initialized.
*
* Note that, for historic reasons, many widgets still do work in the start
* method that would be more suited to the willStart method.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
- return $.when();
+ return Promise.resolve();
},
/**
* Destroys the current widget, also destroys all its children before
* destroying itself.
*/
destroy: function () {
- _.invoke(this.getChildren(), 'destroy');
- if(this.$el) {
+ this.getChildren().forEach(function (child) {
+ child.destroy();
+ });
+ if (this.$el) {
this.$el.remove();
}
mixins.PropertiesMixin.destroy.call(this);
@@ -187,6 +189,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* Renders the current widget and appends it to the given jQuery object.
*
* @param {jQuery} target
+ * @returns {Promise}
*/
appendTo: function (target) {
var self = this;
@@ -198,6 +201,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* Attach the current widget to a dom element
*
* @param {jQuery} target
+ * @returns {Promise}
*/
attachTo: function (target) {
var self = this;
@@ -234,6 +238,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* object.
*
* @param {jQuery} target
+ * @returns {Promise}
*/
insertAfter: function (target) {
var self = this;
@@ -246,6 +251,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* object.
*
* @param {jQuery} target
+ * @returns {Promise}
*/
insertBefore: function (target) {
var self = this;
@@ -257,6 +263,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* Renders the current widget and prepends it to the given jQuery object.
*
* @param {jQuery} target
+ * @returns {Promise}
*/
prependTo: function (target) {
var self = this;
@@ -282,6 +289,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* Renders the current widget and replaces the given jQuery object.
*
* @param target A jQuery object or a Widget instance.
+ * @returns {Promise}
*/
replace: function (target) {
return this._widgetRenderAndInsert(_.bind(function (t) {
@@ -415,7 +423,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* @private
* @param {function: jQuery -> any} insertion
* @param {jQuery} target
- * @returns {Deferred}
+ * @returns {Promise}
*/
_widgetRenderAndInsert: function (insertion, target) {
var self = this;
diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index edc7c03ac02..822c90c31f4 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -184,7 +184,7 @@ var AbstractField = Widget.extend({
* When a field widget is appended to the DOM, its start method is called,
* and will automatically call render. Most widgets should not override this.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
var self = this;
@@ -236,7 +236,7 @@ var AbstractField = Widget.extend({
* if the value changed but was not notified.
*
* @abstract
- * @returns {Deferred|undefined}
+ * @returns {Promise|undefined}
*/
commitChanges: function () {},
/**
@@ -290,12 +290,12 @@ var AbstractField = Widget.extend({
* is optional, and may be used by a widget to share information from the
* moment a field change event is triggered to the moment a reset
* operation is applied.
- * @returns {Deferred} A Deferred, which resolves when the widget rendering
+ * @returns {Promise} A promise, which resolves when the widget rendering
* is complete
*/
reset: function (record, event) {
this._reset(record, event);
- return this._render() || $.when();
+ return this._render() || Promise.resolve();
},
/**
* Remove the invalid class on a field
@@ -396,7 +396,7 @@ var AbstractField = Widget.extend({
* synchronous.
*
* @private
- * @returns {Deferred|undefined}
+ * @returns {Promise|undefined}
*/
_render: function () {
if (this.attrs.decorations) {
@@ -413,7 +413,7 @@ var AbstractField = Widget.extend({
* concrete widget.
*
* @private
- * @returns {Deferred|undefined}
+ * @returns {Promise|undefined}
*/
_renderEdit: function () {
},
@@ -422,7 +422,7 @@ var AbstractField = Widget.extend({
* the concrete widget.
*
* @private
- * @returns {Deferred|undefined}
+ * @returns {Promise|undefined}
*/
_renderReadonly: function () {
},
@@ -457,13 +457,13 @@ var AbstractField = Widget.extend({
* will not notify and not trigger the onchange, even though it was changed.
* @param {boolean} [options.forceChange=false] if true, the change event will be
* triggered even if the new value is the same as the old one
- * @returns {Deferred}
+ * @returns {Promise}
*/
_setValue: function (value, options) {
// we try to avoid doing useless work, if the value given has not
// changed. Note that we compare the unparsed values.
if (this.lastSetValue === value || (this.value === false && value === '')) {
- return $.when();
+ return Promise.resolve();
}
this.lastSetValue = value;
try {
@@ -472,25 +472,26 @@ var AbstractField = Widget.extend({
} catch (e) {
this._isValid = false;
this.trigger_up('set_dirty', {dataPointID: this.dataPointID});
- return $.Deferred().reject();
+ return Promise.reject({message: "Value set is not valid"});
}
if (!(options && options.forceChange) && this._isSameValue(value)) {
- return $.when();
+ return Promise.resolve();
}
- var def = $.Deferred();
- var changes = {};
- changes[this.name] = value;
- this.trigger_up('field_changed', {
- dataPointID: this.dataPointID,
- changes: changes,
- viewType: this.viewType,
- doNotSetDirty: options && options.doNotSetDirty,
- notifyChange: !options || options.notifyChange !== false,
- allowWarning: options && options.allowWarning,
- onSuccess: def.resolve.bind(def),
- onFailure: def.reject.bind(def),
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ var changes = {};
+ changes[self.name] = value;
+ self.trigger_up('field_changed', {
+ dataPointID: self.dataPointID,
+ changes: changes,
+ viewType: self.viewType,
+ doNotSetDirty: options && options.doNotSetDirty,
+ notifyChange: !options || options.notifyChange !== false,
+ allowWarning: options && options.allowWarning,
+ onSuccess: resolve,
+ onFailure: reject,
+ });
});
- return def;
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index c6df3885a86..a87ff3e4436 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -213,7 +213,7 @@ var InputField = DebouncedField.extend({
// sure they are recomputed.
this._applyDecorations();
}
- return $.when();
+ return Promise.resolve();
} else {
return this._render();
}
@@ -486,8 +486,6 @@ var LinkButton = AbstractField.extend({
});
-
-
var FieldDate = InputField.extend({
className: "o_field_date",
tagName: "span",
@@ -513,7 +511,7 @@ var FieldDate = InputField.extend({
*/
start: function () {
var self = this;
- var def;
+ var prom;
if (this.mode === 'edit') {
this.datewidget = this._makeDatePicker();
this.datewidget.on('datetime_changed', this, function () {
@@ -522,13 +520,13 @@ var FieldDate = InputField.extend({
this._setValue(value);
}
});
- def = this.datewidget.appendTo('<div>').done(function () {
+ prom = this.datewidget.appendTo('<div>').then(function () {
self.datewidget.$el.addClass(self.$el.attr('class'));
self._prepareInput(self.datewidget.$input);
self._replaceElement(self.datewidget.$el);
});
}
- return $.when(def, this._super.apply(this, arguments));
+ return Promise.resolve(prom).then(this._super.bind(this));
},
//--------------------------------------------------------------------------
@@ -728,7 +726,7 @@ var FieldMonetary = InputField.extend({
this.$el.empty();
// Prepare and add the input
- this._prepareInput(this.$input).appendTo(this.$el);
+ var def = this._prepareInput(this.$input).appendTo(this.$el);
if (this.currency) {
// Prepare and add the currency symbol
@@ -739,6 +737,7 @@ var FieldMonetary = InputField.extend({
this.$el.prepend($currencySymbol);
}
}
+ return def;
},
/**
* @override
@@ -1136,7 +1135,7 @@ var FieldText = InputField.extend(TranslatableFieldMixin, {
*/
reset: function () {
var self = this;
- return $.when(this._super.apply(this, arguments)).then(function () {
+ return Promise.resolve(this._super.apply(this, arguments)).then(function () {
if (self.mode === 'edit') {
self.$input.trigger('change');
}
@@ -2568,7 +2567,7 @@ var JournalDashboardGraph = AbstractField.extend({
if (this._isInDOM) {
return this._renderInDOM();
}
- return $.when();
+ return Promise.resolve();
},
/**
* Render the widget. This function assumes that it is attached to the DOM.
@@ -2743,13 +2742,13 @@ var FieldDomain = AbstractField.extend({
/**
* @private
* @override _render from AbstractField
- * @returns {Deferred}
+ * @returns {Promise}
*/
_render: function () {
// If there is no model, only change the non-domain-selector content
if (!this._domainModel) {
this._replaceContent();
- return $.when();
+ return Promise.resolve();
}
// Convert char value to array value
@@ -2901,7 +2900,7 @@ var AceEditor = DebouncedField.extend({
/**
* @override start from AbstractField (Widget)
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this._startAce(this.$('.ace-view-editor')[0]);
diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js
index 35d10c5a5e4..0e0ba3dc117 100644
--- a/addons/web/static/src/js/fields/field_registry.js
+++ b/addons/web/static/src/js/fields/field_registry.js
@@ -6,7 +6,7 @@ var Registry = require('web.Registry');
return new Registry();
});
-odoo.define('web._field_registry', function(require) {
+odoo.define('web._field_registry', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index bd46faf1e08..469cedb8ff1 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -171,11 +171,11 @@ var FieldMany2One = AbstractField.extend({
* is saved.
*
* @override
- * @returns {Deferred} resolved as soon as there is no longer record being
+ * @returns {Promise} resolved as soon as there is no longer record being
* (quick) created
*/
commitChanges: function () {
- return $.when(this.createDef);
+ return Promise.resolve(this.createDef);
},
/**
* @override
@@ -208,7 +208,7 @@ var FieldMany2One = AbstractField.extend({
this.isDirty = false;
}
if (this.isDirty) {
- return $.when();
+ return Promise.resolve();
} else {
return this._render();
}
@@ -257,9 +257,9 @@ var FieldMany2One = AbstractField.extend({
if (!source.validation || source.validation.call(self, req.term)) {
source.loading = true;
- // Wrap the returned value of the source.method with $.when.
+ // Wrap the returned value of the source.method with a promise
// So event if the returned value is not async, it will work
- $.when(source.method.call(self, req.term)).then(function (results) {
+ Promise.resolve(source.method.call(self, req.term)).then(function (results) {
source.results = results;
source.loading = false;
resp(self._concatenateAutocompleteResults());
@@ -366,49 +366,55 @@ var FieldMany2One = AbstractField.extend({
/**
* @private
* @param {string} name
- * @returns {Deferred} resolved after the name_create or when the slowcreate
+ * @returns {Promise} resolved after the name_create or when the slowcreate
* modal is closed.
*/
_quickCreate: function (name) {
var self = this;
- var def = $.Deferred();
- this.createDef = this.createDef || $.Deferred();
- // called when the record has been quick created, or when the dialog has
- // been closed (in the case of a 'slow' create), meaning that the job is
- // done
- var createDone = function () {
- def.resolve();
- self.createDef.resolve();
- self.createDef = undefined;
- };
- // called if the quick create is disabled on this many2one, or if the
- // quick creation failed (probably because there are mandatory fields on
- // the model)
- var slowCreate = function () {
- var dialog = self._searchCreatePopup("form", false, self._createContext(name));
- dialog.on('closed', self, createDone);
- };
- if (this.nodeOptions.quick_create) {
- var nameCreateDef = this._rpc({
- model: this.field.relation,
- method: 'name_create',
- args: [name],
- context: this.record.getContext(this.recordParams),
- }).fail(function (error, ev) {
- ev.preventDefault();
- slowCreate();
+ var createDone;
+
+ var def = new Promise(function (resolve, reject) {
+ self.createDef = new Promise(function (innerResolve) {
+ // called when the record has been quick created, or when the dialog has
+ // been closed (in the case of a 'slow' create), meaning that the job is
+ // done
+ createDone = function () {
+ innerResolve();
+ resolve();
+ self.createDef = undefined;
+ };
});
- this.dp.add(nameCreateDef)
- .then(function (result) {
- if (self.mode === "edit") {
- self.reinitialize({id: result[0], display_name: result[1]});
- }
- createDone();
- })
- .fail(def.reject.bind(def));
- } else {
- slowCreate();
- }
+
+ // called if the quick create is disabled on this many2one, or if the
+ // quick creation failed (probably because there are mandatory fields on
+ // the model)
+ var slowCreate = function () {
+ var dialog = self._searchCreatePopup("form", false, self._createContext(name));
+ dialog.on('closed', self, createDone);
+ };
+ if (self.nodeOptions.quick_create) {
+ var nameCreateDef = self._rpc({
+ model: self.field.relation,
+ method: 'name_create',
+ args: [name],
+ context: self.record.getContext(self.recordParams),
+ }).guardedCatch(function (reason) {
+ reason.event.preventDefault();
+ slowCreate();
+ });
+ self.dp.add(nameCreateDef)
+ .then(function (result) {
+ if (self.mode === "edit") {
+ self.reinitialize({id: result[0], display_name: result[1]});
+ }
+ createDone();
+ })
+ .guardedCatch(reject);
+ } else {
+ slowCreate();
+ }
+ });
+
return def;
},
/**
@@ -456,35 +462,32 @@ var FieldMany2One = AbstractField.extend({
*
* @private
* @param {string} search_val
- * @returns {Deferred}
+ * @returns {Promise}
*/
_search: function (search_val) {
var self = this;
- var def = $.Deferred();
- this.orderer.add(def);
-
- var context = this.record.getContext(this.recordParams);
- var domain = this.record.getDomain(this.recordParams);
+ var def = new Promise(function (resolve, reject) {
+ var context = self.record.getContext(self.recordParams);
+ var domain = self.record.getDomain(self.recordParams);
- // Add the additionalContext
- _.extend(context, this.additionalContext);
+ // Add the additionalContext
+ _.extend(context, self.additionalContext);
- var blacklisted_ids = this._getSearchBlacklist();
- if (blacklisted_ids.length > 0) {
- domain.push(['id', 'not in', blacklisted_ids]);
- }
+ var blacklisted_ids = self._getSearchBlacklist();
+ if (blacklisted_ids.length > 0) {
+ domain.push(['id', 'not in', blacklisted_ids]);
+ }
- this._rpc({
- model: this.field.relation,
- method: "name_search",
- kwargs: {
- name: search_val,
- args: domain,
- operator: "ilike",
- limit: this.limit + 1,
- context: context,
- }})
- .then(function (result) {
+ self._rpc({
+ model: self.field.relation,
+ method: "name_search",
+ kwargs: {
+ name: search_val,
+ args: domain,
+ operator: "ilike",
+ limit: self.limit + 1,
+ context: context,
+ }}).then(function (result) {
// possible selections for the m2o
var values = _.map(result, function (x) {
x[1] = self._getDisplayName(x[1]);
@@ -516,7 +519,7 @@ var FieldMany2One = AbstractField.extend({
},
});
}
- $.when(prom).then(function (results) {
+ Promise.resolve(prom).then(function (results) {
var dynamicFilters;
if (results) {
var ids = _.map(results, function (x) {
@@ -563,9 +566,10 @@ var FieldMany2One = AbstractField.extend({
});
}
- def.resolve(values);
+ resolve(values);
});
-
+ });
+ this.orderer.add(def);
return def;
},
/**
@@ -670,8 +674,9 @@ var FieldMany2One = AbstractField.extend({
readonly: !self.can_write,
on_saved: function (record, changed) {
if (changed) {
- self._setValue(self.value.data, {forceChange: true});
- self.trigger_up('reload', {db_id: self.value.id});
+ self._setValue(self.value.data, {forceChange: true}).then(function() {
+ self.trigger_up('reload', {db_id: self.value.id});
+ });
}
},
}).open();
@@ -868,14 +873,14 @@ var FieldX2Many = AbstractField.extend({
* user if he wants to discard it if necessary.
*
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
commitChanges: function () {
var self = this;
var inEditionRecordID =
- this.renderer
- && this.renderer.viewType === "list"
- && this.renderer.getEditableRecordID();
+ this.renderer &&
+ this.renderer.viewType === "list" &&
+ this.renderer.getEditableRecordID();
if (inEditionRecordID) {
return this.renderer.commitChanges(inEditionRecordID).then(function () {
return self._saveLine(inEditionRecordID);
@@ -894,7 +899,7 @@ var FieldX2Many = AbstractField.extend({
* @param {Object} record
* @param {OdooEvent} [ev] an event that triggered the reset action
* @param {Boolean} [fieldChanged] if true, the widget field has changed
- * @returns {Deferred}
+ * @returns {Promise}
*/
reset: function (record, ev, fieldChanged) {
// If 'fieldChanged' is false, it means that the reset was triggered by
@@ -904,7 +909,7 @@ var FieldX2Many = AbstractField.extend({
if (!fieldChanged) {
var newEval = this._evalColumnInvisibleFields();
if (_.isEqual(this.currentColInvisibleFields, newEval)) {
- return $.when();
+ return Promise.resolve();
}
} else if (ev && ev.target === this && ev.data.changes && this.view.arch.tag === 'tree') {
var command = ev.data.changes[this.name];
@@ -994,17 +999,20 @@ var FieldX2Many = AbstractField.extend({
*
* @override
* @private
- * @returns {Deferred|undefined}
+ * @returns {Promise|undefined}
*/
_render: function () {
+ var self = this;
if (!this.view) {
return this._super();
}
if (this.renderer) {
this.currentColInvisibleFields = this._evalColumnInvisibleFields();
- this.renderer.updateState(this.value, {'columnInvisibleFields': this.currentColInvisibleFields});
- this.pager.updateState({ size: this.value.count });
- return $.when();
+ return this.renderer.updateState(this.value, {
+ columnInvisibleFields: this.currentColInvisibleFields,
+ }).then(function () {
+ self.pager.updateState({ size: self.value.count });
+ });
}
var arch = this.view.arch;
var viewType;
@@ -1050,11 +1058,11 @@ var FieldX2Many = AbstractField.extend({
* Prepends the control panel's $el to this widget's $el.
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderControlPanel: function () {
if (!this.view) {
- return $.when();
+ return Promise.resolve();
}
var self = this;
var defs = [];
@@ -1072,7 +1080,7 @@ var FieldX2Many = AbstractField.extend({
validate: function () {
var isList = self.view.arch.tag === 'tree';
// TODO: we should have some common method in the basic renderer...
- return isList ? self.renderer.unselectRow() : $.when();
+ return isList ? self.renderer.unselectRow() : Promise.resolve();
},
});
this.pager.on('pager_changed', this, function (new_state) {
@@ -1089,7 +1097,7 @@ var FieldX2Many = AbstractField.extend({
this._renderButtons();
defs.push(this.pager.appendTo($('<div>'))); // start the pager
defs.push(cpDef);
- return $.when.apply($, defs).then(function () {
+ return Promise.all(defs).then(function () {
self._controlPanel.updateContents({
cp_content: {
$buttons: self.$buttons,
@@ -1123,24 +1131,24 @@ var FieldX2Many = AbstractField.extend({
*
* @private
* @param {string} recordID
- * @returns {Deferred} resolved if the line was properly saved or discarded.
+ * @returns {Promise} resolved if the line was properly saved or discarded.
* rejected if the line could not be saved and the user
* did not agree to discard.
*/
_saveLine: function (recordID) {
var self = this;
- var def = $.Deferred();
- var fieldNames = this.renderer.canBeSaved(recordID);
- if (fieldNames.length) {
- this.trigger_up('discard_changes', {
- recordID: recordID,
- onSuccess: def.resolve.bind(def),
- onFailure: def.reject.bind(def),
- });
- } else {
- this.renderer.setRowMode(recordID, 'readonly').done(def.resolve.bind(def));
- }
- return def.then(function () {
+ return new Promise(function (resolve, reject) {
+ var fieldNames = self.renderer.canBeSaved(recordID);
+ if (fieldNames.length) {
+ self.trigger_up('discard_changes', {
+ recordID: recordID,
+ onSuccess: resolve,
+ onFailure: reject,
+ });
+ } else {
+ self.renderer.setRowMode(recordID, 'readonly').then(resolve);
+ }
+ }).then(function () {
self.pager.updateState({ size: self.value.count });
var newEval = self._evalColumnInvisibleFields();
if (!_.isEqual(self.currentColInvisibleFields, newEval)) {
@@ -1233,7 +1241,7 @@ var FieldX2Many = AbstractField.extend({
this.trigger_up('edited_list', { id: this.value.id });
var editedRecord = this.value.data[ev.data.index];
this.renderer.setRowMode(editedRecord.id, 'edit')
- .done(ev.data.onSuccess);
+ .then(ev.data.onSuccess);
},
/**
* Updates the given record with the changes.
@@ -1260,11 +1268,11 @@ var FieldX2Many = AbstractField.extend({
operation: 'UPDATE',
id: ev.data.dataPointID,
data: changes,
- }).done(function () {
+ }).then(function () {
if (ev.data.onSuccess) {
ev.data.onSuccess();
}
- }).fail(function () {
+ }).guardedCatch(function () {
if (ev.data.onFailure) {
ev.data.onFailure();
}
@@ -1332,8 +1340,8 @@ var FieldX2Many = AbstractField.extend({
self.trigger_up('mutexify', {
action: function () {
return self._saveLine(ev.data.recordID)
- .done(ev.data.onSuccess)
- .fail(ev.data.onFailure);
+ .then(ev.data.onSuccess)
+ .guardedCatch(ev.data.onFailure);
},
});
});
@@ -1361,18 +1369,20 @@ var FieldX2Many = AbstractField.extend({
notifyChange: false,
});
});
- $.when.apply($, defs).then(function () {
+ Promise.all(defs).then(function () {
+ function always() {
+ self.trigger_up('toggle_column_order', {
+ id: self.value.id,
+ name: event.data.handleField,
+ });
+ }
+
// trigger only once the onchange for parent record
self._setValue({
operation: 'UPDATE',
id: rowID,
data: _.object([event.data.handleField], [event.data.offset + rowIDs.length]),
- }).always(function () {
- self.trigger_up('toggle_column_order', {
- id: self.value.id,
- name: event.data.handleField,
- });
- });
+ }).then(always).guardedCatch(always);
});
},
/**
@@ -1418,7 +1428,7 @@ var FieldOne2Many = FieldX2Many.extend({
* @override
* @param {Object} record
* @param {OdooEvent} [ev] an event that triggered the reset action
- * @returns {Deferred}
+ * @returns {Promise}
*/
reset: function (record, ev) {
var self = this;
@@ -1527,13 +1537,16 @@ var FieldOne2Many = FieldX2Many.extend({
context: data.context,
}, {
allowWarning: data.allowWarning
- }).always(function () {
+ }).then(function () {
self.creatingRecord = false;
- }).done(function (){
+ }).then(function (){
if (data.onSuccess){
data.onSuccess();
}
- });
+ }).guardedCatch(function() {
+ self.creatingRecord = false;
+ })
+ ;
}
} else {
this._openFormDialog({
@@ -1646,8 +1659,10 @@ var FieldMany2Many = FieldX2Many.extend({
domain: this.record.getDomain(this.recordParams),
fields_view: this.attrs.views && this.attrs.views.form,
on_saved: function () {
- self._setValue({operation: 'TRIGGER_ONCHANGE'}, {forceChange: true});
- self.trigger_up('reload', {db_id: ev.data.id});
+ self._setValue({operation: 'TRIGGER_ONCHANGE'}, {forceChange: true})
+ .then(function () {
+ self.trigger_up('reload', {db_id: ev.data.id});
+ });
},
on_remove: function () {
self._setValue({operation: 'FORGET', ids: [ev.data.id]});
@@ -1918,10 +1933,12 @@ var FieldMany2ManyTags = AbstractField.extend({
* @override
*/
reset: function (record, event) {
- this._super.apply(this, arguments);
- if (event && event.target === this) {
- this.activate();
- }
+ var self = this;
+ return this._super.apply(this, arguments).then(function(){
+ if (event && event.target === self) {
+ self.activate();
+ }
+ });
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js
index dd91ca2efc2..8dd1f9d3ae8 100644
--- a/addons/web/static/src/js/fields/upgrade_fields.js
+++ b/addons/web/static/src/js/fields/upgrade_fields.js
@@ -34,7 +34,7 @@ var AbstractFieldUpgrade = {
* Redirects the user to the odoo-enterprise/uprade page
*
* @private
- * @returns {Deferred}
+ * @returns {Promise}
*/
_confirmUpgrade: function () {
return this._rpc({
diff --git a/addons/web/static/src/js/public/public_root.js b/addons/web/static/src/js/public/public_root.js
index 7c508bd9192..97448408282 100644
--- a/addons/web/static/src/js/public/public_root.js
+++ b/addons/web/static/src/js/public/public_root.js
@@ -49,11 +49,11 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, {
*/
willStart: function () {
// TODO would be even greater to wait for localeDef only when necessary
- return $.when(
+ return Promise.all([
this._super.apply(this, arguments),
session.is_bound,
localeDef
- );
+ ]);
},
/**
* @override
@@ -82,7 +82,7 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, {
$('input, textarea').placeholder();
}
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
//--------------------------------------------------------------------------
@@ -193,9 +193,9 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, {
self.publicWidgets.push(widget);
return widget.attachTo($(el));
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Destroys all registered widget instances. Website would need this before
@@ -259,8 +259,8 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, {
*/
_onWidgetsStartRequest: function (ev) {
this._startWidgets(ev.data.$target, ev.data.options)
- .done(ev.data.onSuccess)
- .fail(ev.data.onFailure);
+ .then(ev.data.onSuccess)
+ .guardedCatch(ev.data.onFailure);
},
/**
* Called when the root is notified that the public widgets have to be
diff --git a/addons/web/static/src/js/public/public_widget.js b/addons/web/static/src/js/public/public_widget.js
index ec9c50ad9a8..79382e117d8 100644
--- a/addons/web/static/src/js/public/public_widget.js
+++ b/addons/web/static/src/js/public/public_widget.js
@@ -43,7 +43,7 @@ var RootWidget = Widget.extend({
defs.push(this._attachComponents());
this._listenToUpdates = true;
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
//--------------------------------------------------------------------------
@@ -71,7 +71,7 @@ var RootWidget = Widget.extend({
self._widgets.push(w);
return w.attachTo(element);
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Instantiates the child widgets that need to be according to the linked
@@ -88,7 +88,7 @@ var RootWidget = Widget.extend({
var defs = _.map(childInfos, function (childInfo) {
return self._attachComponent(childInfo, $from);
});
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* Returns the `RootWidgetRegistry` instance that is linked to this
diff --git a/addons/web/static/src/js/report/client_action.js b/addons/web/static/src/js/report/client_action.js
index 099ee77a527..c8b5621e6de 100644
--- a/addons/web/static/src/js/report/client_action.js
+++ b/addons/web/static/src/js/report/client_action.js
@@ -37,7 +37,7 @@ var ReportAction = AbstractAction.extend({
start: function () {
var self = this;
this.iframe = this.$('iframe')[0];
- return $.when(this._super.apply(this, arguments), session.is_bound).then(function () {
+ return Promise.all([this._super.apply(this, arguments), session.is_bound]).then(function () {
var web_base_url = session['web.base.url'];
var trusted_host = utils.get_host_from_url(web_base_url);
var trusted_protocol = utils.get_protocol_from_url(web_base_url);
@@ -105,7 +105,7 @@ var ReportAction = AbstractAction.extend({
/**
* Helper allowing to send a message to the `this.el` iframe's window and
- * seting the `targetOrigin` as `this.trusted_origin` (which is the
+ * seting the `targetOrigin` as `this.trusted_origin` (which is the
* `web.base.url` ir.config_parameter key) - in other word, only when using
* this method we only send the message to a trusted domain.
*/
diff --git a/addons/web/static/src/js/services/ajax_service.js b/addons/web/static/src/js/services/ajax_service.js
index 0cecaae9da3..cb83dd9d553 100644
--- a/addons/web/static/src/js/services/ajax_service.js
+++ b/addons/web/static/src/js/services/ajax_service.js
@@ -7,19 +7,20 @@ var session = require('web.session');
var AjaxService = AbstractService.extend({
rpc: function (route, args, options, target) {
- var def = $.Deferred();
- var promise = def.promise();
- var xhrDef = session.rpc(route, args, options);
- promise.abort = xhrDef.abort.bind(xhrDef);
- xhrDef.then(function () {
- if (!target.isDestroyed()) {
- def.resolve.apply(def, arguments);
- }
- }).fail(function () {
- if (!target.isDestroyed()) {
- def.reject.apply(def, arguments);
- }
+ var rpcPromise;
+ var promise = new Promise(function (resolve, reject) {
+ rpcPromise = session.rpc(route, args, options);
+ rpcPromise.then(function (result) {
+ if (!target.isDestroyed()) {
+ resolve(result);
+ }
+ }).guardedCatch(function (reason) {
+ if (!target.isDestroyed()) {
+ reject(reason);
+ }
+ });
});
+ promise.abort = rpcPromise.abort.bind(rpcPromise);
return promise;
},
});
diff --git a/addons/web/static/src/js/services/core.js b/addons/web/static/src/js/services/core.js
index 3d6cfc4e684..ee37479e217 100644
--- a/addons/web/static/src/js/services/core.js
+++ b/addons/web/static/src/js/services/core.js
@@ -13,15 +13,15 @@ var translation = require('web.translation');
*
* @type Boolean
*/
-var bus = new Bus ();
+var bus = new Bus();
-_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
- $('html').on(evtype, function(ev) {
+_.each('click,dblclick,keydown,keypress,keyup'.split(','), function (evtype) {
+ $('html').on(evtype, function (ev) {
bus.trigger(evtype, ev);
});
});
-_.each('resize,scroll'.split(','), function(evtype) {
- $(window).on(evtype, function(ev) {
+_.each('resize,scroll'.split(','), function (evtype) {
+ $(window).on(evtype, function (ev) {
bus.trigger(evtype, ev);
});
});
@@ -37,7 +37,7 @@ return {
_lt: translation._lt,
// registries
- action_registry : new Registry(),
+ action_registry: new Registry(),
crash_registry: new Registry(),
serviceRegistry: new Registry(),
/**
diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js
index db88d688505..a29e21ca8ca 100644
--- a/addons/web/static/src/js/services/crash_manager.js
+++ b/addons/web/static/src/js/services/crash_manager.js
@@ -65,7 +65,7 @@ var CrashManager = core.Class.extend({
ajax.jsonRpc('/web/webclient/version_info', 'call', {}, {shadow:true}).then(function () {
core.bus.trigger('connection_restored');
self.isConnected = true;
- }).fail(function () {
+ }).guardedCatch(function () {
// exponential backoff, with some jitter
delay = (delay * 1.5) + 500*Math.random();
setTimeout(checkConnection, delay);
diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js
index 348054691bc..b2f6ef1feb1 100644
--- a/addons/web/static/src/js/services/data_manager.js
+++ b/addons/web/static/src/js/services/data_manager.js
@@ -33,7 +33,7 @@ return core.Class.extend({
*
* @param {int|string} [action_id] the action id or xmlid
* @param {Object} [additional_context] used to load the action
- * @return {Deferred} resolved with the action whose id or xmlid is action_id
+ * @return {Promise} resolved with the action whose id or xmlid is action_id
*/
load_action: function (action_id, additional_context) {
var self = this;
@@ -69,7 +69,7 @@ return core.Class.extend({
* - options.load_filters: whether or not to load the filters,
* - options.action_id: the action_id (required to load filters),
* - options.toolbar: whether or not a toolbar will be displayed,
- * @return {Deferred} resolved with the requested views information
+ * @return {Promise} resolved with the requested views information
*/
load_views: function (params, options) {
var self = this;
@@ -108,12 +108,12 @@ return core.Class.extend({
var fvg = result.fields_views[view_descr[1]];
fvg.viewFields = fvg.fields;
fvg.fields = result.fields;
- self._cache.fields_views[fv_key] = $.when(fvg);
+ self._cache.fields_views[fv_key] = Promise.resolve(fvg);
});
// Insert filters, if any, into the filters cache
if (result.filters) {
- self._cache.filters[filters_key] = $.when(result.filters);
+ self._cache.filters[filters_key] = Promise.resolve(result.filters);
}
return result.fields_views;
@@ -130,7 +130,7 @@ return core.Class.extend({
* @param {string} params.modelName
* @param {Object} params.context
* @param {integer} params.actionId
- * @return {Deferred} resolved with the requested filters
+ * @return {Promise} resolved with the requested filters
*/
load_filters: function (params) {
var key = this._gen_key(params.modelName, params.actionId);
@@ -143,7 +143,7 @@ return core.Class.extend({
},
model: 'ir.filters',
method: 'get_filters',
- }).fail(this._invalidate.bind(this, this._cache.filters, key));
+ }).guardedCatch(this._invalidate.bind(this, this._cache.filters, key));
}
return this._cache.filters[key];
},
@@ -152,7 +152,7 @@ return core.Class.extend({
* Calls 'create_or_replace' on 'ir_filters'.
*
* @param {Object} [filter] the filter description
- * @return {Deferred} resolved with the id of the created or replaced filter
+ * @return {Promise} resolved with the id of the created or replaced filter
*/
create_filter: function (filter) {
var self = this;
@@ -175,7 +175,7 @@ return core.Class.extend({
* Calls 'unlink' on 'ir_filters'.
*
* @param {integer} filterId Id of the filter to remove
- * @return {Deferred}
+ * @return {Promise}
*/
delete_filter: function (filterId) {
var self = this;
diff --git a/addons/web/static/src/js/services/report_service.js b/addons/web/static/src/js/services/report_service.js
index 006c546041a..ed4907a6d6d 100644
--- a/addons/web/static/src/js/services/report_service.js
+++ b/addons/web/static/src/js/services/report_service.js
@@ -15,7 +15,7 @@ var ReportService = AbstractService.extend({
* Checks the state of the installation of wkhtmltopdf on the server.
* Implements an internal cache to do the request only once.
*
- * @returns {Deferred} resolved with the state of wkhtmltopdf on the server
+ * @returns {Promise} resolved with the state of wkhtmltopdf on the server
* (possible values are 'ok', 'broken', 'install', 'upgrade', 'workers').
*/
checkWkhtmltopdf: function () {
diff --git a/addons/web/static/src/js/tools/debug_manager.js b/addons/web/static/src/js/tools/debug_manager.js
index f09d8745f04..172148e03ef 100644
--- a/addons/web/static/src/js/tools/debug_manager.js
+++ b/addons/web/static/src/js/tools/debug_manager.js
@@ -62,7 +62,7 @@ var DebugManager = Widget.extend({
this._has_features = false;
// whether the current user is an administrator
this._is_admin = session.is_system;
- return $.when(
+ return Promise.resolve(
this._rpc({
model: 'res.users',
method: 'check_access_rights',
@@ -160,7 +160,7 @@ var DebugManager = Widget.extend({
.append(QWeb.render('WebClient.DebugManager.Global', {
manager: this,
}));
- return $.when();
+ return Promise.resolve();
},
select_view: function () {
var self = this;
@@ -281,7 +281,7 @@ DebugManager.include({
model: 'ir.model',
method: 'search',
args: [[['model', '=', model]]]
- }).done(function (ids) {
+ }).then(function (ids) {
self.do_action({
res_model: 'ir.model.fields',
name: _t('View Fields'),
@@ -324,7 +324,7 @@ DebugManager.include({
DebugManager.include({
start: function () {
this._can_edit_views = false;
- return $.when(
+ return Promise.all([
this._super(),
this._rpc({
model: 'ir.ui.view',
@@ -334,6 +334,7 @@ DebugManager.include({
.then(function (ar) {
this._can_edit_views = ar;
}.bind(this))
+ ]
);
},
update: function (tag, descriptor, widget) {
@@ -382,7 +383,7 @@ DebugManager.include({
model: this._action.res_model,
method: 'get_metadata',
args: [selectedIDs],
- }).done(function(result) {
+ }).then(function(result) {
var metadata = result[0];
metadata.creator = field_utils.format.many2one(metadata.create_uid);
metadata.lastModifiedBy = field_utils.format.many2one(metadata.write_uid);
@@ -515,7 +516,7 @@ DebugManager.include({
true,
condition || false,
],
- }).done(function () { d.close(); });
+ }).then(function () { d.close(); });
}}
]
});
diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js
index 4e31b2d4467..d950d5d56ee 100644
--- a/addons/web/static/src/js/tools/test_menus.js
+++ b/addons/web/static/src/js/tools/test_menus.js
@@ -65,16 +65,16 @@
}
console.log('Found ', $listOfAppMenuItems.length, 'apps to test');
- var testDef = $.when();
- testDef = chainDeferred($listOfAppMenuItems, testDef, testApp);
- return testDef.then(function() {
+ var testPromise = Promise.resolve();
+ testPromise = chainDeferred($listOfAppMenuItems, testPromise, testApp);
+ return testPromise.then(function () {
console.log("Successfully tested ", testedApps.length, " apps");
console.log("Successfully tested ", testedMenus.length - testedApps.length, " menus");
console.log("test successful");
- }).always(function() {
- console.log("Test took ", (performance.now() - startTime)/1000, " seconds");
- }).fail(function () {
- console.error("test failed");
+ console.log("Test took ", (performance.now() - startTime) / 1000, " seconds");
+ }).catch(function () {
+ console.log("Test took ", (performance.now() - startTime) / 1000, " seconds");
+ console.error("Error !");
});
}
@@ -86,18 +86,18 @@
* 3 - clicking on each menu
* 3.1 - clicking on each view
* @param {DomElement} element: the App menu item
- * @returns {Deferred}
+ * @returns {Promise}
*/
function testApp(element){
console.log("Testing app menu:", element.dataset.menuXmlid);
- if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Another infinite loop protection
+ if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return Promise.resolve(); // Another infinite loop protection
testedApps.push(element.dataset.menuXmlid);
return testMenuItem(element).then(function () {
var $subMenuItems;
$subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4');
- var testMenuDef = $.when();
- testMenuDef = chainDeferred($subMenuItems, testMenuDef, testMenuItem);
- return testMenuDef;
+ var testMenuPromise = Promise.resolve();
+ testMenuPromise = chainDeferred($subMenuItems, testMenuPromise, testMenuItem);
+ return testMenuPromise;
}).then(function(){
// no effect in community
var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th");
@@ -112,10 +112,10 @@
* 2 - Orchestrate the view switch
*
* @param {DomElement} element: the menu item
- * @returns {Deferred}
+ * @returns {Promise}
*/
function testMenuItem(element){
- if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Avoid infinite loop
+ if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return Promise.resolve(); // Avoid infinite loop
console.log("Testing menu", element.innerText.trim(), " ", element.dataset.menuXmlid);
testedMenus.push(element.dataset.menuXmlid);
var startActionCount = clientActionCount;
@@ -128,7 +128,7 @@
$modal.modal('hide');
isModal = true;
return true;
- };
+ }
return startActionCount != clientActionCount;
}).then(function() {
if (!isModal) {
@@ -138,7 +138,7 @@
if (!isModal) {
return testViews();
}
- }).fail(function() {
+ }).catch(function () {
console.error("Error while testing", element);
});
};
@@ -151,21 +151,21 @@
*/
function testViews() {
var $switches = $("nav.o_cp_switch_buttons > button:not(.active):visible");
- var testSwitchDef = $.when();
- // chainDeferred($switches, testSwitchDef, testViewSwitch # FIXME
+ var testSwitchPromise = Promise.resolve();
+ // chainDeferred($switches, testSwitchPromise, testViewSwitch # FIXME
_.each($switches, function(switchButton) {
- testSwitchDef = testSwitchDef.then(function () {
+ testSwitchPromise = testSwitchPromise.then(function () {
// get the view view-type data attribute
return testViewSwitch(switchButton.dataset.viewType);
});
});
- return testSwitchDef;
+ return testSwitchPromise;
}
/**
* Test a view button
* @param {string} viewType: a string for the view type to test (list, kanban ...)
- * @returns {Deferred} a deferred that wait for the view to be loaded
+ * @returns {Promise} a promise that wait for the view to be loaded
*/
function testViewSwitch(viewType){
console.log("Testing view switch: ", viewType);
@@ -188,20 +188,20 @@
* Click on each filter in the control pannel
*/
function testFilters() {
- var filterDef = $.when();
+ var filterProm = Promise.resolve();
// var $filters = $('div.o_control_panel div.btn-group.o_dropdown > ul.o_filters_menu > li:not(.o_add_custom_filter)');
var $filters = $('.o_filters_menu > .o_menu_item');
console.log("Testing " + $filters.length + " filters");
var filter_ids = _.compact(_.map($filters, function(f) { return f.dataset.id}));
_.each(filter_ids, function(filter_id){
- filterDef = filterDef.then(function(){
+ filterProm = filterProm.then(function(){
var currentViewCount = viewUpdateCount;
var $filter = $('.o_menu_item[data-id="' + filter_id + '"] a');
// with some customized search views, the filter cannot be found
if ($filter[0] === undefined) {
console.warn('Filter with ID ', filter_id , 'cannot be found');
- return $.Deferred().resolve();
- };
+ return Promise.resolve();
+ }
console.log('Clicking on filter "', $filter.text().trim(), '"');
$filter[0].click();
setTimeout(function() {
@@ -220,52 +220,55 @@
});
});
});
- return filterDef;
+ return filterProm;
}
// utility functions
/**
* Wait a certain amount of time for a condition to occur
- * @param stopCondition: a function that returns a boolean
- * @returns {Deferred} that is rejected if the timeout is exceeded
+ * @param {function} stopCondition a function that returns a boolean
+ * @returns {Promise} that is rejected if the timeout is exceeded
*/
function waitForCondition(stopCondition) {
- var def = $.Deferred();
- var interval = 250;
- var timeLimit = 15000;
- function checkCondition() {
- if (stopCondition()) {
- def.resolve();
- } else {
- timeLimit -= interval;
- if (timeLimit > 0) {
- // recursive call until the resolve or the timeout
- setTimeout(checkCondition, interval);
+ var prom = new Promise(function (resolve, reject) {
+ var interval = 250;
+ var timeLimit = 15000;
+
+ function checkCondition() {
+ if (stopCondition()) {
+ resolve();
} else {
- console.error("Timeout exceeded", stopCondition);
- def.reject();
+ timeLimit -= interval;
+ if (timeLimit > 0) {
+ // recursive call until the resolve or the timeout
+ setTimeout(checkCondition, interval);
+ } else {
+ console.error("Timeout exceeded", stopCondition);
+ reject();
+ }
}
}
- }
- setTimeout(checkCondition, interval);
- return def;
- };
+ setTimeout(checkCondition, interval);
+ });
+ return prom;
+ }
/**
- * chain deferred actions
- * @param $elements: a list of jquery elements to be passed as arg to the function
- * @param deferred: the deferred on which other deferreds will be chained
- * @param f: the function to be deferred
- * @returns : the chained deferred
+ * Chain deferred actions.
+ *
+ * @param {jQueryElement} $elements a list of jquery elements to be passed as arg to the function
+ * @param {Promise} promise the promise on which other promises will be chained
+ * @param {function} f the function to be deferred
+ * @returns {Promise} the chained promise
*/
- function chainDeferred($elements, deferred, f) {
+ function chainDeferred($elements, promise, f) {
_.each($elements, function(el) {
- deferred = deferred.then(function () {
+ promise = promise.then(function () {
return f(el);
});
});
- return deferred;
+ return promise;
}
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) {
diff --git a/addons/web/static/src/js/widgets/attach_document.js b/addons/web/static/src/js/widgets/attach_document.js
index 13496f3aea7..3175875c151 100644
--- a/addons/web/static/src/js/widgets/attach_document.js
+++ b/addons/web/static/src/js/widgets/attach_document.js
@@ -108,26 +108,27 @@ var AttachDocument = Widget.extend({
* @private
*/
_onFileLoaded: function () {
- var self = this,
- def = $.Deferred();
-
- if (self.node.attrs.action) {
- // the first argument isn't a file but the jQuery.Event
- var files = Array.prototype.slice.call(arguments, 1);
- self._rpc({
- model: self.res_model,
- method: self.node.attrs.action,
- args: [self.res_id],
- kwargs: {
- 'attachment_ids': _.map(files, function (file) {return file.id;}),
- }
- }).then( function () {
- def.resolve();
- });
- } else {
- def.resolve();
- }
- return $.when(def).then(function () {
+ var self = this;
+ return new Promise(function (resolve) {
+ if (self.node.attrs.action) {
+ // the first argument isn't a file but the jQuery.Event
+ var files = Array.prototype.slice.call(arguments, 1);
+ self._rpc({
+ model: self.res_model,
+ method: self.node.attrs.action,
+ args: [self.res_id],
+ kwargs: {
+ attachment_ids: _.map(files, function (file) {
+ return file.id;
+ }),
+ }
+ }).then(function () {
+ resolve();
+ });
+ } else {
+ resolve();
+ }
+ }).then(function () {
self.trigger_up('reload');
framework.unblockUI();
});
diff --git a/addons/web/static/src/js/widgets/change_password.js b/addons/web/static/src/js/widgets/change_password.js
index c05fa30408b..e75d8dfda38 100644
--- a/addons/web/static/src/js/widgets/change_password.js
+++ b/addons/web/static/src/js/widgets/change_password.js
@@ -20,7 +20,7 @@ var ChangePassword = AbstractAction.extend({
* @fixme: weird interaction with the parent for the $buttons handling
*
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
var self = this;
@@ -37,7 +37,7 @@ var ChangePassword = AbstractAction.extend({
fields: $('form[name=change_password_form]').serializeArray()
}
})
- .done(function (result) {
+ .then(function (result) {
if (result.error) {
self._display_error(result);
} else {
diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js
index e24818f7336..a913fcd0ea2 100644
--- a/addons/web/static/src/js/widgets/data_export.js
+++ b/addons/web/static/src/js/widgets/data_export.js
@@ -56,7 +56,7 @@ var DataExport = Dialog.extend({
*/
start: function () {
var self = this;
- var defs = [this._super.apply(this, arguments)];
+ var proms = [this._super.apply(this, arguments)];
// The default for the ".modal_content" element is "max-height: 100%;"
// but we want it to always expand to "height: 100%;" for this modal.
@@ -67,10 +67,10 @@ var DataExport = Dialog.extend({
this.$fieldsList = this.$('.o_fields_list');
this.$importCompatRadios = this.$('.o_import_compat input');
- defs.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats));
- defs.push(this._onChangeCompatibleInput());
+ proms.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats));
+ proms.push(this._onChangeCompatibleInput());
- defs.push(this.getParent().getActiveDomain().then(function (domain) {
+ proms.push(this.getParent().getActiveDomain().then(function (domain) {
if (domain === undefined) {
self.idsToExport = self.getParent().getSelectedIds();
self.domain = self.record.domain;
@@ -80,7 +80,7 @@ var DataExport = Dialog.extend({
}
}));
- defs.push(this._showExportsList().then(function () {
+ proms.push(this._showExportsList().then(function () {
_.each(self.records, function (record, key) {
if (_.contains(self.defaultExportFields, key)) {
self._addField(key, record.string);
@@ -88,12 +88,12 @@ var DataExport = Dialog.extend({
});
}));
- return $.when.apply($, defs);
+ return Promise.all(proms);
- function doSetupExportFormats (formats) {
+ function doSetupExportFormats(formats) {
var $fmts = self.$('.o_export_format');
- _.each(formats, function (format, i) {
+ _.each(formats, function (format) {
var $radio = $('<input/>', {type: 'radio', value: format.tag, name: 'o_export_format_name'});
var $label = $('<span/>', {html: format.label});
@@ -192,16 +192,16 @@ var DataExport = Dialog.extend({
*/
_onExpandAction: function (record) {
var self = this;
- if (!record['children']) {
+ if (!record.children) {
return;
}
- var model = record['params']['model'];
- var prefix = record['params']['prefix'];
- var name = record['params']['name'];
+ var model = record.params.model;
+ var prefix = record.params.prefix;
+ var name = record.params.name;
var excludeFields = [];
- if (record['relation_field']) {
- excludeFields.push(record['relation_field']);
+ if (record.relation_field) {
+ excludeFields.push(record.relation_field);
}
if (!record.loaded) {
@@ -212,11 +212,11 @@ var DataExport = Dialog.extend({
prefix: prefix,
parent_name: name,
import_compat: !!this.$importCompatRadios.filter(':checked').val(),
- parent_field_type: record['field_type'],
- parent_field: record['params']['parent_field'],
+ parent_field_type: record.field_type,
+ parent_field: record.params.parent_field,
exclude: excludeFields,
},
- }).done(function (results) {
+ }).then(function (results) {
record.loaded = true;
self._onShowData(results, record.id);
});
@@ -294,7 +294,7 @@ var DataExport = Dialog.extend({
var self = this;
if (this.$('.o_exported_lists_select').is(':hidden')) {
this.$('.o_exported_lists').show();
- return $.when();
+ return Promise.resolve();
}
return this._rpc({
@@ -355,7 +355,7 @@ var DataExport = Dialog.extend({
model: this.record.model,
import_compat: this.isCompatibleMode,
},
- }).done(function (records) {
+ }).then(function (records) {
var compatibleFields = _.map(records, function (record) { return record.id; });
self.$fieldsList
.find('option')
diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js
index 8e20e1ce18d..ed71679699c 100644
--- a/addons/web/static/src/js/widgets/domain_selector.js
+++ b/addons/web/static/src/js/widgets/domain_selector.js
@@ -188,11 +188,14 @@ var DomainTree = DomainNode.extend({
},
/**
* @see DomainNode.start
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this._postRender();
- return $.when(this._super.apply(this, arguments), this._renderChildrenTo(this.$childrenContainer));
+ return Promise.all([
+ this._super.apply(this, arguments),
+ this._renderChildrenTo(this.$childrenContainer)
+ ]);
},
//--------------------------------------------------------------------------
@@ -369,11 +372,11 @@ var DomainTree = DomainNode.extend({
*
* @private
* @param {jQuery} $to - the jQuery node to which the children must be added
- * @returns {Deferred}
+ * @returns {Promise}
*/
_renderChildrenTo: function ($to) {
var $div = $("<div/>");
- return $.when.apply($, _.map(this.children, (function (child) {
+ return Promise.all(_.map(this.children, (function (child) {
return child.appendTo($div);
}).bind(this))).then((function () {
_.each(this.children, function (child) {
@@ -485,11 +488,11 @@ var DomainSelector = DomainTree.extend({
* does nothing.
*
* @param {string} domain
- * @returns {Deferred} resolved when the rerendering is finished
+ * @returns {Promise} resolved when the rerendering is finished
*/
setDomain: function (domain) {
if (domain === Domain.prototype.arrayToString(this.getDomain())) {
- return $.when();
+ return Promise.resolve();
}
var parsedDomain = this._parseDomain(domain);
if (parsedDomain) {
@@ -542,7 +545,7 @@ var DomainSelector = DomainTree.extend({
* This method is ugly but achieves the right behavior without flickering.
*
* @param {Array|string} domain
- * @returns {Deferred}
+ * @returns {Promise}
*/
_redraw: function (domain) {
var oldChildren = this.children.slice();
@@ -637,7 +640,7 @@ var DomainLeaf = DomainNode.extend({
* Prepares the information the rendering of the widget will need by
* pre-instantiating its internal field selector widget.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
willStart: function () {
var defs = [this._super.apply(this, arguments)];
@@ -702,17 +705,17 @@ var DomainLeaf = DomainNode.extend({
}).bind(this)));
}
- return $.when.apply($, wDefs);
+ return Promise.all(wDefs);
}
}).bind(this)));
- return $.when.apply($, defs);
+ return Promise.all(defs);
},
/**
* @see DomainNode.start
* Appends the prepared field selector and value widget.
*
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this.fieldSelector.$el.prependTo(this.$("> .o_domain_leaf_info, > .o_domain_leaf_edition")); // place the field selector
diff --git a/addons/web/static/src/js/widgets/domain_selector_dialog.js b/addons/web/static/src/js/widgets/domain_selector_dialog.js
index 46b54089c5f..f6fe7f0c479 100644
--- a/addons/web/static/src/js/widgets/domain_selector_dialog.js
+++ b/addons/web/static/src/js/widgets/domain_selector_dialog.js
@@ -45,10 +45,10 @@ return Dialog.extend({
// this restores default modal height (bootstrap) and allows field selector to overflow
self.$el.css('overflow', 'visible').closest('.modal-dialog').css('height', 'auto');
});
- return $.when(
+ return Promise.all([
this._super.apply(this, arguments),
this.domainSelector.appendTo(this.$el)
- );
+ ]);
},
});
});
diff --git a/addons/web/static/src/js/widgets/iframe_widget.js b/addons/web/static/src/js/widgets/iframe_widget.js
index 77d27fac5b4..8ecbd5f1c7d 100644
--- a/addons/web/static/src/js/widgets/iframe_widget.js
+++ b/addons/web/static/src/js/widgets/iframe_widget.js
@@ -29,7 +29,7 @@ var IFrameWidget = Widget.extend({
},
/**
* @override
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this.$el.css({height: '100%', width: '100%', border: 0});
diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js
index b8c953a2d0e..08a7ae47c81 100644
--- a/addons/web/static/src/js/widgets/model_field_selector.js
+++ b/addons/web/static/src/js/widgets/model_field_selector.js
@@ -112,17 +112,17 @@ var ModelFieldSelector = Widget.extend({
},
/**
* @see Widget.willStart()
- * @returns {Deferred}
+ * @returns {Promise}
*/
willStart: function () {
- return $.when(
+ return Promise.all([
this._super.apply(this, arguments),
this._prefill()
- );
+ ]);
},
/**
* @see Widget.start
- * @returns {Deferred}
+ * @returns {Promise}
*/
start: function () {
this.$value = this.$(".o_field_selector_value");
@@ -162,11 +162,11 @@ var ModelFieldSelector = Widget.extend({
* Saves a new field chain (array) and re-render.
*
* @param {string[]} chain - the new field chain
- * @returns {Deferred} resolved once the re-rendering is finished
+ * @returns {Promise} resolved once the re-rendering is finished
*/
setChain: function (chain) {
if (_.isEqual(chain, this.chain)) {
- return $.when();
+ return Promise.resolve();
}
this.chain = chain;
@@ -290,14 +290,14 @@ var ModelFieldSelector = Widget.extend({
* chain.
*
* @private
- * @returns {Deferred} resolved once the whole field chain has been
+ * @returns {Promise} resolved once the whole field chain has been
* processed
*/
_prefill: function () {
this.pages = [];
return this._pushPageData(this.model).then((function () {
this._validate(true);
- return (this.chain.length ? processChain.call(this, this.chain.slice().reverse()) : $.when());
+ return (this.chain.length ? processChain.call(this, this.chain.slice().reverse()) : Promise.resolve());
}).bind(this));
function processChain(chain) {
@@ -305,11 +305,11 @@ var ModelFieldSelector = Widget.extend({
if (field && field.relation && chain.length > 0) { // Fetch next chain node if any and possible
return this._pushPageData(field.relation).then(processChain.bind(this, chain));
} else if (field && chain.length === 0) { // Last node fetched
- return $.when();
+ return Promise.resolve();
} else { // Wrong node chain
this._validate(false);
}
- return $.when();
+ return Promise.resolve();
}
},
/**
@@ -318,12 +318,12 @@ var ModelFieldSelector = Widget.extend({
*
* @private
* @param {string} model - the model name whose fields have to be fetched
- * @returns {Deferred} resolved once the fields have been added
+ * @returns {Promise} resolved once the fields have been added
*/
_pushPageData: function (model) {
var def;
if (this.model === model && this.options.fields) {
- def = $.when(sortFields(this.options.fields, model, this.options.order));
+ def = Promise.resolve(sortFields(this.options.fields, model, this.options.order));
} else {
def = this._getModelFieldsFromCache(model, this.options.filters);
}
diff --git a/addons/web/static/src/js/widgets/name_and_signature.js b/addons/web/static/src/js/widgets/name_and_signature.js
index c860b0a4361..7c3f05a7148 100644
--- a/addons/web/static/src/js/widgets/name_and_signature.js
+++ b/addons/web/static/src/js/widgets/name_and_signature.js
@@ -80,14 +80,12 @@ var NameAndSignature = Widget.extend({
*/
willStart: function () {
var self = this;
-
- return $.when(
+ return Promise.all([
this._super.apply(this, arguments),
this._rpc({route: '/web/sign/get_fonts/' + self.defaultFont}).then(function (data) {
self.fonts = data;
})
- );
-
+ ]);
},
/**
* Finds the DOM elements, initializes the signature area,
@@ -217,7 +215,7 @@ var NameAndSignature = Widget.extend({
resetSignature: function () {
if (!this.$signatureField) {
// no action if called before start
- return $.Deferred().reject();
+ return Promise.reject();
}
// recompute size based on the current width
this.$signatureField.css({width: 'unset'});
@@ -248,7 +246,7 @@ var NameAndSignature = Widget.extend({
this.focusName();
- return $.Deferred().resolve();
+ return Promise.resolve();
},
/**
* Changes the signature mode. Toggles the display of the relevant
diff --git a/addons/web/static/src/js/widgets/pie_chart.js b/addons/web/static/src/js/widgets/pie_chart.js
index 44a08a5a22a..ddeca1fdcec 100644
--- a/addons/web/static/src/js/widgets/pie_chart.js
+++ b/addons/web/static/src/js/widgets/pie_chart.js
@@ -67,7 +67,7 @@ var PieChart = Widget.extend({
self.controller = controller;
return self.controller.appendTo(document.createDocumentFragment());
});
- return $.when(def1, def2);
+ return Promise.all([def1, def2]);
},
/**
* @override
diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js
index cc0f8825643..a389ee268cd 100644
--- a/addons/web/static/src/js/widgets/switch_company_menu.js
+++ b/addons/web/static/src/js/widgets/switch_company_menu.js
@@ -31,12 +31,6 @@ var SwitchCompanyMenu = Widget.extend({
/**
* @override
*/
- willStart: function () {
- return session.user_companies ? this._super() : $.Deferred().reject();
- },
- /**
- * @override
- */
start: function () {
var companiesList = '';
if (this.isMobile) {
@@ -81,9 +75,9 @@ var SwitchCompanyMenu = Widget.extend({
});
},
});
-
-SystrayMenu.Items.push(SwitchCompanyMenu);
-
+if (session.user_companies) {
+ SystrayMenu.Items.push(SwitchCompanyMenu);
+}
return SwitchCompanyMenu;
});