[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>
This commit is contained in:
committed by
Aaron Bohy
co-authored by
Aaron Bohy
Christophe Matthieu
Mathieu Duckerts-Antoine
David Monjoie
Martin Geubelle
svs-odoo
parent
eeed894fd5
commit
ab56e637b7
@@ -83,18 +83,19 @@ method.
|
||||
|
||||
As required by :js:class:`~openerp.web.Widget`'s contract, if
|
||||
:js:func:`~openerp.web.Widget.start()` executes any asynchronous
|
||||
code it should return a ``$.Deferred`` so callers know when it's
|
||||
code it should return a ``Promise`` so callers know when it's
|
||||
ready for interaction.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
start: function () {
|
||||
return $.when(
|
||||
return Promise.all([
|
||||
this._super(),
|
||||
// Simply read the res.widget object this action should display
|
||||
new instance.web.Model('res.widget').call(
|
||||
'read', [[this.widget_id], ['title']])
|
||||
.then(this.proxy('on_widget_loaded'));
|
||||
.then(this.proxy('on_widget_loaded')
|
||||
]);
|
||||
}
|
||||
|
||||
The client action can then behave exactly as it wishes to within its
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
@@ -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;
|
||||
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);
|
||||
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.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: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.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');
|
||||
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();
|
||||
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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
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);
|
||||
});
|
||||
$.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));
|
||||
});
|
||||
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) {
|
||||
self.$el.removeClass('o_mobile_menu_opened');
|
||||
|
||||
return self.action_mutex.exec(function () {
|
||||
var completed = $.Deferred();
|
||||
$.when(self._openMenu(result, {
|
||||
clear_breadcrumbs: true,
|
||||
})).always(function () {
|
||||
completed.resolve();
|
||||
var completed = new Promise(function (resolve, reject) {
|
||||
Promise.resolve(self._openMenu(result, {
|
||||
clear_breadcrumbs: true,
|
||||
})).then(resolve).guardedCatch(reject);
|
||||
|
||||
setTimeout(function () {
|
||||
resolve();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
completed.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);
|
||||
|
||||
@@ -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'));
|
||||
|
||||
var rejection;
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
rejection = reject;
|
||||
|
||||
result.then(function (result) {
|
||||
if (!shadow) {
|
||||
core.bus.trigger('rpc_response');
|
||||
}
|
||||
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()});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: jsonp?
|
||||
promise.abort = function () {
|
||||
rejection({
|
||||
message: "XmlHttpRequestError abort",
|
||||
event: $.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") {
|
||||
if (!shadow) {
|
||||
core.bus.trigger('rpc_response');
|
||||
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);
|
||||
}
|
||||
if (error.code === 100) {
|
||||
core.bus.trigger('invalidate_session');
|
||||
}
|
||||
deferred.reject(error, $.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]
|
||||
},
|
||||
};
|
||||
deferred.reject(nerror, $.Event());
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
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
|
||||
// "all the calls" are finished
|
||||
if (!url || !qweb) {
|
||||
return allLoadingsDef;
|
||||
}
|
||||
|
||||
// If the given URL has already been seen, do nothing but returning the
|
||||
// associated deferred
|
||||
if (_.contains(seenURLs, url)) {
|
||||
var oldLoadingData = _.findWhere(loadingsData, {url: url});
|
||||
return oldLoadingData ? oldLoadingData.def : $.when();
|
||||
}
|
||||
seenURLs.push(url);
|
||||
|
||||
// Add the information about the new data to load: the url, the qweb
|
||||
// engine and the associated deferred
|
||||
var newLoadingData = {
|
||||
url: url,
|
||||
qweb: qweb,
|
||||
def: $.Deferred(),
|
||||
};
|
||||
loadingsData.push(newLoadingData);
|
||||
|
||||
// If not already started, start the loading loop (reinitialize the
|
||||
// "all the calls" deferred to an unresolved state)
|
||||
if (!isLoading) {
|
||||
allLoadingsDef = $.Deferred();
|
||||
_load();
|
||||
}
|
||||
|
||||
// Return the deferred 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
|
||||
// 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 deferred
|
||||
// loadXML with the same URL returns the right promise
|
||||
loadingsData.shift();
|
||||
loadingData.def.resolve();
|
||||
loadingData.resolve();
|
||||
_load();
|
||||
});
|
||||
} else {
|
||||
// There is nothing to load anymore, so resolve the
|
||||
// "all the calls" deferred
|
||||
// "all the calls" promise
|
||||
isLoading = false;
|
||||
allLoadingsDef.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// If no argument, simply returns the promise which indicates when
|
||||
// "all the calls" are finished
|
||||
if (!url || !qweb) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// If the given URL has already been seen, do nothing but returning the
|
||||
// associated promise
|
||||
if (_.contains(seenURLs, url)) {
|
||||
var oldLoadingData = _.findWhere(loadingsData, {url: url});
|
||||
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 promise
|
||||
var newLoadingData = {
|
||||
url: url,
|
||||
qweb: qweb,
|
||||
};
|
||||
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" promise to an unresolved state)
|
||||
if (!isLoading) {
|
||||
_load();
|
||||
}
|
||||
|
||||
// Return the promise associated to the new given URL
|
||||
return newLoadingData.def;
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
reject(reason);
|
||||
} else if (shouldReject) {
|
||||
reject();
|
||||
}
|
||||
}).always(function () {
|
||||
if (reject) {
|
||||
// noop if def already resolved or rejected
|
||||
def.reject();
|
||||
}
|
||||
// otherwise leave promise in limbo
|
||||
});
|
||||
}).promise();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Inform the object it should destroy itself, releasing any
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 def = new Promise(function (resolve, reject) {
|
||||
var context = self.record.getContext(self.recordParams);
|
||||
var domain = self.record.getDomain(self.recordParams);
|
||||
|
||||
var context = this.record.getContext(this.recordParams);
|
||||
var domain = this.record.getDomain(this.recordParams);
|
||||
// Add the additionalContext
|
||||
_.extend(context, self.additionalContext);
|
||||
|
||||
// Add the additionalContext
|
||||
_.extend(context, this.additionalContext);
|
||||
var blacklisted_ids = self._getSearchBlacklist();
|
||||
if (blacklisted_ids.length > 0) {
|
||||
domain.push(['id', 'not in', blacklisted_ids]);
|
||||
}
|
||||
|
||||
var blacklisted_ids = this._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();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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(); });
|
||||
}}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
+12
-12
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
// 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 = [];
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
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');
|
||||
});
|
||||
$dropdown.appendTo($result);
|
||||
}
|
||||
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 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);
|
||||
|
||||
// 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"),
|
||||
}));
|
||||
|
||||
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();
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
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 isGrouped = !!this.state.groupedBy.length;
|
||||
var fragment = document.createDocumentFragment();
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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());
|
||||
$table.append(this._renderHeader());
|
||||
$table.append(this._renderBody());
|
||||
$table.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);
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -72,7 +72,7 @@ var PivotRenderer = AbstractRenderer.extend({
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
* @returns {Deferred}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_render: function () {
|
||||
if (!this._hasContent()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ var IFrameWidget = Widget.extend({
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @returns {Deferred}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
start: function () {
|
||||
this.$el.css({height: '100%', width: '100%', border: 0});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,12 +28,6 @@ var SwitchCompanyMenu = Widget.extend({
|
||||
this.isMobile = config.device.isMobile;
|
||||
this._onClick = _.debounce(this._onClick, 1500, true);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: function () {
|
||||
return session.user_companies ? this._super() : $.Deferred().reject();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -81,9 +75,9 @@ var SwitchCompanyMenu = Widget.extend({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
SystrayMenu.Items.push(SwitchCompanyMenu);
|
||||
|
||||
if (session.user_companies) {
|
||||
SystrayMenu.Items.push(SwitchCompanyMenu);
|
||||
}
|
||||
return SwitchCompanyMenu;
|
||||
|
||||
});
|
||||
|
||||
@@ -53,23 +53,23 @@ QUnit.module('ActionManager', {
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test('uses a mobile-friendly view by default (if possible)', function (assert) {
|
||||
QUnit.test('uses a mobile-friendly view by default (if possible)', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var actionManager = createActionManager({
|
||||
var actionManager = await createActionManager({
|
||||
actions: this.actions,
|
||||
archs: this.archs,
|
||||
data: this.data,
|
||||
});
|
||||
|
||||
// should default on a mobile-friendly view (kanban) for action 1
|
||||
actionManager.doAction(1);
|
||||
await actionManager.doAction(1);
|
||||
|
||||
assert.containsNone(actionManager, '.o_list_view');
|
||||
assert.containsOnce(actionManager, '.o_kanban_view');
|
||||
|
||||
// there is no mobile-friendly view for action 2, should use the first one (list)
|
||||
actionManager.doAction(2);
|
||||
await actionManager.doAction(2);
|
||||
|
||||
assert.containsOnce(actionManager, '.o_list_view');
|
||||
assert.containsNone(actionManager, '.o_kanban_view');
|
||||
@@ -77,10 +77,10 @@ QUnit.module('ActionManager', {
|
||||
actionManager.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('lazy load mobile-friendly view', function (assert) {
|
||||
QUnit.test('lazy load mobile-friendly view', async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
var actionManager = createActionManager({
|
||||
var actionManager = await createActionManager({
|
||||
actions: this.actions,
|
||||
archs: this.archs,
|
||||
data: this.data,
|
||||
@@ -89,7 +89,7 @@ QUnit.module('ActionManager', {
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
actionManager.loadState({
|
||||
await actionManager.loadState({
|
||||
action: 1,
|
||||
view_type: 'form',
|
||||
});
|
||||
@@ -99,7 +99,7 @@ QUnit.module('ActionManager', {
|
||||
assert.containsOnce(actionManager, '.o_form_view');
|
||||
|
||||
// go back to lazy loaded view
|
||||
testUtils.dom.click(actionManager.$('.o_control_panel .breadcrumb .breadcrumb-item:first'));
|
||||
await testUtils.dom.click(actionManager.$('.o_control_panel .breadcrumb .breadcrumb-item:first'));
|
||||
assert.containsNone(actionManager, '.o_form_view');
|
||||
assert.containsNone(actionManager, '.o_list_view');
|
||||
assert.containsOnce(actionManager, '.o_kanban_view');
|
||||
@@ -114,16 +114,16 @@ QUnit.module('ActionManager', {
|
||||
actionManager.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('view switcher button should be displayed in dropdown on mobile screens', function (assert) {
|
||||
QUnit.test('view switcher button should be displayed in dropdown on mobile screens', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var actionManager = createActionManager({
|
||||
var actionManager = await createActionManager({
|
||||
actions: this.actions,
|
||||
archs: this.archs,
|
||||
data: this.data,
|
||||
});
|
||||
|
||||
actionManager.doAction(1);
|
||||
await actionManager.doAction(1);
|
||||
|
||||
assert.containsOnce(actionManager.$('.o_control_panel'), '.o_cp_switch_buttons button[data-toggle="dropdown"]');
|
||||
assert.hasClass(actionManager.$('.o_cp_switch_buttons .o_cp_switch_kanban'), 'active');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,14 @@ var testUtils = require('web.test_utils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
QUnit.module('KeyboardNavigationMixin', function () {
|
||||
QUnit.test('aria-keyshortcuts is added on elements with accesskey', function (assert) {
|
||||
QUnit.test('aria-keyshortcuts is added on elements with accesskey', async function (assert) {
|
||||
assert.expect(1);
|
||||
var $target = $('#qunit-fixture');
|
||||
var KeyboardWidget = Widget.extend(KeyboardNavigationMixin, {
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
KeyboardNavigationMixin.init.call(this);
|
||||
},
|
||||
start: function () {
|
||||
var $button = $('<button>').text('Click Me!').attr('accesskey', 'o');
|
||||
// we need to define the accesskey because it will not be assigned on invisible buttons
|
||||
@@ -19,7 +23,7 @@ QUnit.module('KeyboardNavigationMixin', function () {
|
||||
});
|
||||
var parent = testUtils.createParent({});
|
||||
var w = new KeyboardWidget(parent);
|
||||
w.appendTo($target);
|
||||
await w.appendTo($target);
|
||||
|
||||
// minimum set of attribute to generate a native event that works with the mixin
|
||||
var e = new Event("keydown");
|
||||
@@ -32,7 +36,7 @@ QUnit.module('KeyboardNavigationMixin', function () {
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('keep CSS position absolute for parent of overlay', function (assert) {
|
||||
QUnit.test('keep CSS position absolute for parent of overlay', async function (assert) {
|
||||
// If we change the CSS position of an 'absolute' element to 'relative',
|
||||
// we may likely change its position on the document. Since the overlay
|
||||
// CSS position is 'absolute', it will match the size and cover the
|
||||
@@ -42,6 +46,10 @@ QUnit.module('KeyboardNavigationMixin', function () {
|
||||
var $target = $('#qunit-fixture');
|
||||
var $button;
|
||||
var KeyboardWidget = Widget.extend(KeyboardNavigationMixin, {
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
KeyboardNavigationMixin.init.call(this);
|
||||
},
|
||||
start: function () {
|
||||
$button = $('<button>').text('Click Me!').attr('accesskey', 'o');
|
||||
// we need to define the accesskey because it will not be assigned on invisible buttons
|
||||
@@ -51,7 +59,7 @@ QUnit.module('KeyboardNavigationMixin', function () {
|
||||
});
|
||||
var parent = testUtils.createParent({});
|
||||
var w = new KeyboardWidget(parent);
|
||||
w.appendTo($target);
|
||||
await w.appendTo($target);
|
||||
|
||||
$button.css('position', 'absolute');
|
||||
|
||||
|
||||
@@ -9,40 +9,41 @@ QUnit.module('chrome', {}, function () {
|
||||
|
||||
QUnit.module('Pager');
|
||||
|
||||
QUnit.test('basic stuff', function (assert) {
|
||||
QUnit.test('basic stuff', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var pager = new Pager(null, 10, 1, 4);
|
||||
pager.appendTo($('#qunit-fixture'));
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual(pager.state.current_min, 1,
|
||||
"current_min should be set to 1");
|
||||
|
||||
// click on next
|
||||
testUtils.dom.click(pager.$('.o_pager_next'));
|
||||
await testUtils.dom.click(pager.$('.o_pager_next'));
|
||||
assert.strictEqual(pager.state.current_min, 5,
|
||||
"current_min should now be 5");
|
||||
|
||||
pager.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('edit the pager', function (assert) {
|
||||
QUnit.test('edit the pager', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var pager = new Pager(null, 10, 1, 4);
|
||||
pager.appendTo($('#qunit-fixture'));
|
||||
await testUtils.nextTick();
|
||||
|
||||
// enter edition
|
||||
testUtils.dom.click(pager.$('.o_pager_value'));
|
||||
await testUtils.dom.click(pager.$('.o_pager_value'));
|
||||
assert.containsOnce(pager, 'input',
|
||||
"the pager should contain an input");
|
||||
assert.strictEqual(pager.$('input').val(), '1-4',
|
||||
"the input should have correct value");
|
||||
|
||||
// change the limit
|
||||
pager.$('input')
|
||||
.val('1-6')
|
||||
.trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); // trigger the change
|
||||
await testUtils.fields.editInput(pager.$('input'), '1-6');
|
||||
await testUtils.fields.triggerKeydown(pager.$('input'), 'enter');
|
||||
assert.strictEqual(pager.state.limit, 6,
|
||||
"the limit should have been updated");
|
||||
assert.strictEqual(pager.state.current_min, 1,
|
||||
@@ -53,43 +54,40 @@ QUnit.module('chrome', {}, function () {
|
||||
pager.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('disabling the pager', function (assert) {
|
||||
var done = assert.async();
|
||||
QUnit.test('disabling the pager', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var pager = new Pager(null, 10, 1, 4);
|
||||
pager.appendTo($('#qunit-fixture'));
|
||||
|
||||
pager.disable();
|
||||
await testUtils.nextTick();
|
||||
await pager.disable();
|
||||
|
||||
// try to go to the next or previous pages
|
||||
testUtils.dom.click(pager.$('.o_pager_next'));
|
||||
await testUtils.dom.click(pager.$('.o_pager_next'));
|
||||
assert.strictEqual(pager.state.current_min, 1,
|
||||
"current_min should still be 1");
|
||||
testUtils.dom.click(pager.$('.o_pager_previous'));
|
||||
await testUtils.dom.click(pager.$('.o_pager_previous'));
|
||||
assert.strictEqual(pager.state.current_min, 1,
|
||||
"current_min should still be 1");
|
||||
|
||||
// try to change the limit
|
||||
testUtils.dom.click(pager.$('.o_pager_value'));
|
||||
await testUtils.dom.click(pager.$('.o_pager_value'));
|
||||
assert.containsNone(pager, 'input',
|
||||
"the pager should not contain an input");
|
||||
|
||||
// a common use is to disable the pager before reloading the data, and
|
||||
// re-enable it once they have been loaded
|
||||
// the following imulates this situation
|
||||
// the following emulates this situation
|
||||
pager.on('pager_changed', null, function () {
|
||||
pager.disable();
|
||||
concurrency.delay(0).then(function () {
|
||||
assert.ok(pager.disabled, "pager should still be disabled");
|
||||
pager.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
pager.enable();
|
||||
testUtils.dom.click(pager.$('.o_pager_next'));
|
||||
|
||||
await testUtils.dom.click(pager.$('.o_pager_next'));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user