diff options
| author | Robot Odoo <xmo+roboto@odoo.com> | 2019-03-06 21:18:36 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-03-06 21:18:36 +0100 |
| commit | fc60c49ba3cb30fc4e436c9ffc1661d707fe858c (patch) | |
| tree | 110bf47078ce4862cfad05aa061ba1238ebc99e6 /addons/web/static/src/js/widgets | |
| parent | 414ac1a49a61e7f68b4c8dab8688a5b8840aefed (diff) | |
| parent | 53e7050ce0c1bd873f239bce8ad81ba42c19d57a (diff) | |
[MERGE] web,*: update code base to jQuery 3.*
The goal of this task is to update jQuery from version 1.11 to 3.x.
Task-id : 1896658
As we are skipping 2 magor versions, there are some breaking changes in the jquery library described here: https://jquery.com/upgrade-guide/3.0/
Main changes are:
-------------------------
1. Update the libraries jQuery, jQueryUI, QUnit and select2 to their latest version.
The link to generate the build of jQueryUI with the used components: [jquery UI build](https://jqueryui.com/download/#!version=1.12.1&components=111110010101111110100010110000011010000000000000)
2. Use native promises ([a complete explanation about promises here](https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch3.md)) everywhere possible:
In order to [be A+ compliant](https://promisesaplus.com/), the jq3 deferred uses `setTimeout(..., 0)`
for every then and catch, which takes between 4 and 10 ms by call.
This would be way too slow to use in Odoo.
To prevent this, we have used the native Promises everywhere possible.
Be it with jq deferred or native promises, all promises are now asynchronous,
this example will ALWAYS result in printing first "just after blabla" then "blabla"
because the `then` function will always be asynchronous (opposite to
what jquery used to do)
```
function () {
Promise.resolve("blabla").then(console.log);
console.log("just after blabla");
}
// console:
// just after blabla
// blabla
```
3. On native promises, you should see the catch like a try/catch in
javascript, meaning that it will catch all errors including coding errors:
For this we have introduced the concept of `guardedCatch`, which will be executed
on promise rejection unless the promise is rejected because there is
a code error or runtime error (calls on undefined, etc).
4. Native Promises do not have `done`, `fail` and `always` methods nor does it have the `state` method
- `fail` can be replaced with `guardedCatch`
- `return $.when(x).done(function() ...)` can be replaced with
```
var x = Promise.resolve(); // we get the x promise in a local variable, without then
x.then(function() ...); // we add a new handler to x. This operation returns a new promise we don't care about
return x; // we return the x promise that will be completed without waiting for the then
```
- `$.when().done(function ...)` without the return can be replaced by `then(function ....)`, because if it is not returned, it doesn't matter what is chained or not `$.when().always(function() ...)` can be replaced with
```
var always = function() ...
Promise.resolve().then(always).guardedCatch(always);
```
- It is not possible to inspect the state of a promise, except in debug tools of chrome and firefox. Any code that relies on `$.Deferred.state()` should be rewritten to use flags in `then` and `catch` handlers. Remember that it is not possible to execute the handlers in a synchroneous manner, so when a promise is created, its `then` will never be called on the same execution stack. At best it will be called on the next microtask tick (before the next setTimeout).
5. We have introduced the concept of guardedCatch, to copy the behavior of the old fail:
The guarded catch will only execute the handler in parameter if the rejection reason is not an `Error`
```
var failingPromise = Promise.reject({message:"I am the rejection reason"});
failingPromise.guardedCatch(function(reason) {console.log(reason);}); // is called, then returns a rejected promise
failingPromise.catch(function(reason) {console.log(reason);}); // is called
```
will write the reason "I am the rejection reason" to the console twice: once because the `guardedCatch` will execute (and return a rejected `Promise` with the same reason it was called), a second time because the `catch` will catch the rejected reason returned by the `guardedCatch`
```
var promiseWithError = Promise.resolve().then(function() {
a.b = 0; // <-- the variable a does not exist, so a.b is an error
});
promiseWithError.guardedCatch(function(reason) {console.log(reason);}); // will not be called
promiseWithError.catch(function(reason) {console.log(reason);}); // will be called
```
contrary to how deferred used to work, here the catch will be called because it behaves like a `try... catch...` and will catch the coding error like if it was a business error. The `guardedCatch` however does not call the handler if the rejection reason is an error.
In all Odoo code, we never reject a promise with an error and always use objects to defined the rejected reason, so it is a good idea to use `guardedCatch` everywhere.
6. Native Promises can have only one parameter passed to a `then` function or a `catch` function:
`xxx.then(function(param1, param2) {...}); // param2 will never have a value`
- we can replace `$.when(prom1, prom2).then(function(result1, result2) { ... });` by
```
Promise.all([prom1, prom2]).then(function(results) {
var result1 = results[1];
var result2 = results[2];
...
});
```
7. We are now using ES7 code in the unit tests (async await).
closes odoo/odoo#28457
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Christophe Matthieu <chm@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: David Monjoie <dmo@odoo.com>
Co-authored-by: Martin Geubelle <mge@odoo.com>
Co-authored-by: svs-odoo <svs@odoo.com>
Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
Diffstat (limited to 'addons/web/static/src/js/widgets')
10 files changed, 79 insertions, 83 deletions
diff --git a/addons/web/static/src/js/widgets/attach_document.js b/addons/web/static/src/js/widgets/attach_document.js index 13496f3aea7..3175875c151 100644 --- a/addons/web/static/src/js/widgets/attach_document.js +++ b/addons/web/static/src/js/widgets/attach_document.js @@ -108,26 +108,27 @@ var AttachDocument = Widget.extend({ * @private */ _onFileLoaded: function () { - var self = this, - def = $.Deferred(); - - if (self.node.attrs.action) { - // the first argument isn't a file but the jQuery.Event - var files = Array.prototype.slice.call(arguments, 1); - self._rpc({ - model: self.res_model, - method: self.node.attrs.action, - args: [self.res_id], - kwargs: { - 'attachment_ids': _.map(files, function (file) {return file.id;}), - } - }).then( function () { - def.resolve(); - }); - } else { - def.resolve(); - } - return $.when(def).then(function () { + var self = this; + return new Promise(function (resolve) { + if (self.node.attrs.action) { + // the first argument isn't a file but the jQuery.Event + var files = Array.prototype.slice.call(arguments, 1); + self._rpc({ + model: self.res_model, + method: self.node.attrs.action, + args: [self.res_id], + kwargs: { + attachment_ids: _.map(files, function (file) { + return file.id; + }), + } + }).then(function () { + resolve(); + }); + } else { + resolve(); + } + }).then(function () { self.trigger_up('reload'); framework.unblockUI(); }); diff --git a/addons/web/static/src/js/widgets/change_password.js b/addons/web/static/src/js/widgets/change_password.js index c05fa30408b..e75d8dfda38 100644 --- a/addons/web/static/src/js/widgets/change_password.js +++ b/addons/web/static/src/js/widgets/change_password.js @@ -20,7 +20,7 @@ var ChangePassword = AbstractAction.extend({ * @fixme: weird interaction with the parent for the $buttons handling * * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { var self = this; @@ -37,7 +37,7 @@ var ChangePassword = AbstractAction.extend({ fields: $('form[name=change_password_form]').serializeArray() } }) - .done(function (result) { + .then(function (result) { if (result.error) { self._display_error(result); } else { diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index e24818f7336..a913fcd0ea2 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -56,7 +56,7 @@ var DataExport = Dialog.extend({ */ start: function () { var self = this; - var defs = [this._super.apply(this, arguments)]; + var proms = [this._super.apply(this, arguments)]; // The default for the ".modal_content" element is "max-height: 100%;" // but we want it to always expand to "height: 100%;" for this modal. @@ -67,10 +67,10 @@ var DataExport = Dialog.extend({ this.$fieldsList = this.$('.o_fields_list'); this.$importCompatRadios = this.$('.o_import_compat input'); - defs.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats)); - defs.push(this._onChangeCompatibleInput()); + proms.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats)); + proms.push(this._onChangeCompatibleInput()); - defs.push(this.getParent().getActiveDomain().then(function (domain) { + proms.push(this.getParent().getActiveDomain().then(function (domain) { if (domain === undefined) { self.idsToExport = self.getParent().getSelectedIds(); self.domain = self.record.domain; @@ -80,7 +80,7 @@ var DataExport = Dialog.extend({ } })); - defs.push(this._showExportsList().then(function () { + proms.push(this._showExportsList().then(function () { _.each(self.records, function (record, key) { if (_.contains(self.defaultExportFields, key)) { self._addField(key, record.string); @@ -88,12 +88,12 @@ var DataExport = Dialog.extend({ }); })); - return $.when.apply($, defs); + return Promise.all(proms); - function doSetupExportFormats (formats) { + function doSetupExportFormats(formats) { var $fmts = self.$('.o_export_format'); - _.each(formats, function (format, i) { + _.each(formats, function (format) { var $radio = $('<input/>', {type: 'radio', value: format.tag, name: 'o_export_format_name'}); var $label = $('<span/>', {html: format.label}); @@ -192,16 +192,16 @@ var DataExport = Dialog.extend({ */ _onExpandAction: function (record) { var self = this; - if (!record['children']) { + if (!record.children) { return; } - var model = record['params']['model']; - var prefix = record['params']['prefix']; - var name = record['params']['name']; + var model = record.params.model; + var prefix = record.params.prefix; + var name = record.params.name; var excludeFields = []; - if (record['relation_field']) { - excludeFields.push(record['relation_field']); + if (record.relation_field) { + excludeFields.push(record.relation_field); } if (!record.loaded) { @@ -212,11 +212,11 @@ var DataExport = Dialog.extend({ prefix: prefix, parent_name: name, import_compat: !!this.$importCompatRadios.filter(':checked').val(), - parent_field_type: record['field_type'], - parent_field: record['params']['parent_field'], + parent_field_type: record.field_type, + parent_field: record.params.parent_field, exclude: excludeFields, }, - }).done(function (results) { + }).then(function (results) { record.loaded = true; self._onShowData(results, record.id); }); @@ -294,7 +294,7 @@ var DataExport = Dialog.extend({ var self = this; if (this.$('.o_exported_lists_select').is(':hidden')) { this.$('.o_exported_lists').show(); - return $.when(); + return Promise.resolve(); } return this._rpc({ @@ -355,7 +355,7 @@ var DataExport = Dialog.extend({ model: this.record.model, import_compat: this.isCompatibleMode, }, - }).done(function (records) { + }).then(function (records) { var compatibleFields = _.map(records, function (record) { return record.id; }); self.$fieldsList .find('option') diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js index 8e20e1ce18d..ed71679699c 100644 --- a/addons/web/static/src/js/widgets/domain_selector.js +++ b/addons/web/static/src/js/widgets/domain_selector.js @@ -188,11 +188,14 @@ var DomainTree = DomainNode.extend({ }, /** * @see DomainNode.start - * @returns {Deferred} + * @returns {Promise} */ start: function () { this._postRender(); - return $.when(this._super.apply(this, arguments), this._renderChildrenTo(this.$childrenContainer)); + return Promise.all([ + this._super.apply(this, arguments), + this._renderChildrenTo(this.$childrenContainer) + ]); }, //-------------------------------------------------------------------------- @@ -369,11 +372,11 @@ var DomainTree = DomainNode.extend({ * * @private * @param {jQuery} $to - the jQuery node to which the children must be added - * @returns {Deferred} + * @returns {Promise} */ _renderChildrenTo: function ($to) { var $div = $("<div/>"); - return $.when.apply($, _.map(this.children, (function (child) { + return Promise.all(_.map(this.children, (function (child) { return child.appendTo($div); }).bind(this))).then((function () { _.each(this.children, function (child) { @@ -485,11 +488,11 @@ var DomainSelector = DomainTree.extend({ * does nothing. * * @param {string} domain - * @returns {Deferred} resolved when the rerendering is finished + * @returns {Promise} resolved when the rerendering is finished */ setDomain: function (domain) { if (domain === Domain.prototype.arrayToString(this.getDomain())) { - return $.when(); + return Promise.resolve(); } var parsedDomain = this._parseDomain(domain); if (parsedDomain) { @@ -542,7 +545,7 @@ var DomainSelector = DomainTree.extend({ * This method is ugly but achieves the right behavior without flickering. * * @param {Array|string} domain - * @returns {Deferred} + * @returns {Promise} */ _redraw: function (domain) { var oldChildren = this.children.slice(); @@ -637,7 +640,7 @@ var DomainLeaf = DomainNode.extend({ * Prepares the information the rendering of the widget will need by * pre-instantiating its internal field selector widget. * - * @returns {Deferred} + * @returns {Promise} */ willStart: function () { var defs = [this._super.apply(this, arguments)]; @@ -702,17 +705,17 @@ var DomainLeaf = DomainNode.extend({ }).bind(this))); } - return $.when.apply($, wDefs); + return Promise.all(wDefs); } }).bind(this))); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * @see DomainNode.start * Appends the prepared field selector and value widget. * - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.fieldSelector.$el.prependTo(this.$("> .o_domain_leaf_info, > .o_domain_leaf_edition")); // place the field selector diff --git a/addons/web/static/src/js/widgets/domain_selector_dialog.js b/addons/web/static/src/js/widgets/domain_selector_dialog.js index 46b54089c5f..f6fe7f0c479 100644 --- a/addons/web/static/src/js/widgets/domain_selector_dialog.js +++ b/addons/web/static/src/js/widgets/domain_selector_dialog.js @@ -45,10 +45,10 @@ return Dialog.extend({ // this restores default modal height (bootstrap) and allows field selector to overflow self.$el.css('overflow', 'visible').closest('.modal-dialog').css('height', 'auto'); }); - return $.when( + return Promise.all([ this._super.apply(this, arguments), this.domainSelector.appendTo(this.$el) - ); + ]); }, }); }); diff --git a/addons/web/static/src/js/widgets/iframe_widget.js b/addons/web/static/src/js/widgets/iframe_widget.js index 77d27fac5b4..8ecbd5f1c7d 100644 --- a/addons/web/static/src/js/widgets/iframe_widget.js +++ b/addons/web/static/src/js/widgets/iframe_widget.js @@ -29,7 +29,7 @@ var IFrameWidget = Widget.extend({ }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.$el.css({height: '100%', width: '100%', border: 0}); diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js index b8c953a2d0e..08a7ae47c81 100644 --- a/addons/web/static/src/js/widgets/model_field_selector.js +++ b/addons/web/static/src/js/widgets/model_field_selector.js @@ -112,17 +112,17 @@ var ModelFieldSelector = Widget.extend({ }, /** * @see Widget.willStart() - * @returns {Deferred} + * @returns {Promise} */ willStart: function () { - return $.when( + return Promise.all([ this._super.apply(this, arguments), this._prefill() - ); + ]); }, /** * @see Widget.start - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.$value = this.$(".o_field_selector_value"); @@ -162,11 +162,11 @@ var ModelFieldSelector = Widget.extend({ * Saves a new field chain (array) and re-render. * * @param {string[]} chain - the new field chain - * @returns {Deferred} resolved once the re-rendering is finished + * @returns {Promise} resolved once the re-rendering is finished */ setChain: function (chain) { if (_.isEqual(chain, this.chain)) { - return $.when(); + return Promise.resolve(); } this.chain = chain; @@ -290,14 +290,14 @@ var ModelFieldSelector = Widget.extend({ * chain. * * @private - * @returns {Deferred} resolved once the whole field chain has been + * @returns {Promise} resolved once the whole field chain has been * processed */ _prefill: function () { this.pages = []; return this._pushPageData(this.model).then((function () { this._validate(true); - return (this.chain.length ? processChain.call(this, this.chain.slice().reverse()) : $.when()); + return (this.chain.length ? processChain.call(this, this.chain.slice().reverse()) : Promise.resolve()); }).bind(this)); function processChain(chain) { @@ -305,11 +305,11 @@ var ModelFieldSelector = Widget.extend({ if (field && field.relation && chain.length > 0) { // Fetch next chain node if any and possible return this._pushPageData(field.relation).then(processChain.bind(this, chain)); } else if (field && chain.length === 0) { // Last node fetched - return $.when(); + return Promise.resolve(); } else { // Wrong node chain this._validate(false); } - return $.when(); + return Promise.resolve(); } }, /** @@ -318,12 +318,12 @@ var ModelFieldSelector = Widget.extend({ * * @private * @param {string} model - the model name whose fields have to be fetched - * @returns {Deferred} resolved once the fields have been added + * @returns {Promise} resolved once the fields have been added */ _pushPageData: function (model) { var def; if (this.model === model && this.options.fields) { - def = $.when(sortFields(this.options.fields, model, this.options.order)); + def = Promise.resolve(sortFields(this.options.fields, model, this.options.order)); } else { def = this._getModelFieldsFromCache(model, this.options.filters); } diff --git a/addons/web/static/src/js/widgets/name_and_signature.js b/addons/web/static/src/js/widgets/name_and_signature.js index c860b0a4361..7c3f05a7148 100644 --- a/addons/web/static/src/js/widgets/name_and_signature.js +++ b/addons/web/static/src/js/widgets/name_and_signature.js @@ -80,14 +80,12 @@ var NameAndSignature = Widget.extend({ */ willStart: function () { var self = this; - - return $.when( + return Promise.all([ this._super.apply(this, arguments), this._rpc({route: '/web/sign/get_fonts/' + self.defaultFont}).then(function (data) { self.fonts = data; }) - ); - + ]); }, /** * Finds the DOM elements, initializes the signature area, @@ -217,7 +215,7 @@ var NameAndSignature = Widget.extend({ resetSignature: function () { if (!this.$signatureField) { // no action if called before start - return $.Deferred().reject(); + return Promise.reject(); } // recompute size based on the current width this.$signatureField.css({width: 'unset'}); @@ -248,7 +246,7 @@ var NameAndSignature = Widget.extend({ this.focusName(); - return $.Deferred().resolve(); + return Promise.resolve(); }, /** * Changes the signature mode. Toggles the display of the relevant diff --git a/addons/web/static/src/js/widgets/pie_chart.js b/addons/web/static/src/js/widgets/pie_chart.js index 44a08a5a22a..ddeca1fdcec 100644 --- a/addons/web/static/src/js/widgets/pie_chart.js +++ b/addons/web/static/src/js/widgets/pie_chart.js @@ -67,7 +67,7 @@ var PieChart = Widget.extend({ self.controller = controller; return self.controller.appendTo(document.createDocumentFragment()); }); - return $.when(def1, def2); + return Promise.all([def1, def2]); }, /** * @override diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js index cc0f8825643..a389ee268cd 100644 --- a/addons/web/static/src/js/widgets/switch_company_menu.js +++ b/addons/web/static/src/js/widgets/switch_company_menu.js @@ -31,12 +31,6 @@ var SwitchCompanyMenu = Widget.extend({ /** * @override */ - willStart: function () { - return session.user_companies ? this._super() : $.Deferred().reject(); - }, - /** - * @override - */ start: function () { var companiesList = ''; if (this.isMobile) { @@ -81,9 +75,9 @@ var SwitchCompanyMenu = Widget.extend({ }); }, }); - -SystrayMenu.Items.push(SwitchCompanyMenu); - +if (session.user_companies) { + SystrayMenu.Items.push(SwitchCompanyMenu); +} return SwitchCompanyMenu; }); |
