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/tools | |
| 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/tools')
| -rw-r--r-- | addons/web/static/src/js/tools/debug_manager.js | 13 | ||||
| -rw-r--r-- | addons/web/static/src/js/tools/test_menus.js | 109 |
2 files changed, 63 insertions, 59 deletions
diff --git a/addons/web/static/src/js/tools/debug_manager.js b/addons/web/static/src/js/tools/debug_manager.js index f09d8745f04..172148e03ef 100644 --- a/addons/web/static/src/js/tools/debug_manager.js +++ b/addons/web/static/src/js/tools/debug_manager.js @@ -62,7 +62,7 @@ var DebugManager = Widget.extend({ this._has_features = false; // whether the current user is an administrator this._is_admin = session.is_system; - return $.when( + return Promise.resolve( this._rpc({ model: 'res.users', method: 'check_access_rights', @@ -160,7 +160,7 @@ var DebugManager = Widget.extend({ .append(QWeb.render('WebClient.DebugManager.Global', { manager: this, })); - return $.when(); + return Promise.resolve(); }, select_view: function () { var self = this; @@ -281,7 +281,7 @@ DebugManager.include({ model: 'ir.model', method: 'search', args: [[['model', '=', model]]] - }).done(function (ids) { + }).then(function (ids) { self.do_action({ res_model: 'ir.model.fields', name: _t('View Fields'), @@ -324,7 +324,7 @@ DebugManager.include({ DebugManager.include({ start: function () { this._can_edit_views = false; - return $.when( + return Promise.all([ this._super(), this._rpc({ model: 'ir.ui.view', @@ -334,6 +334,7 @@ DebugManager.include({ .then(function (ar) { this._can_edit_views = ar; }.bind(this)) + ] ); }, update: function (tag, descriptor, widget) { @@ -382,7 +383,7 @@ DebugManager.include({ model: this._action.res_model, method: 'get_metadata', args: [selectedIDs], - }).done(function(result) { + }).then(function(result) { var metadata = result[0]; metadata.creator = field_utils.format.many2one(metadata.create_uid); metadata.lastModifiedBy = field_utils.format.many2one(metadata.write_uid); @@ -515,7 +516,7 @@ DebugManager.include({ true, condition || false, ], - }).done(function () { d.close(); }); + }).then(function () { d.close(); }); }} ] }); diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js index 4e31b2d4467..d950d5d56ee 100644 --- a/addons/web/static/src/js/tools/test_menus.js +++ b/addons/web/static/src/js/tools/test_menus.js @@ -65,16 +65,16 @@ } console.log('Found ', $listOfAppMenuItems.length, 'apps to test'); - var testDef = $.when(); - testDef = chainDeferred($listOfAppMenuItems, testDef, testApp); - return testDef.then(function() { + var testPromise = Promise.resolve(); + testPromise = chainDeferred($listOfAppMenuItems, testPromise, testApp); + return testPromise.then(function () { console.log("Successfully tested ", testedApps.length, " apps"); console.log("Successfully tested ", testedMenus.length - testedApps.length, " menus"); console.log("test successful"); - }).always(function() { - console.log("Test took ", (performance.now() - startTime)/1000, " seconds"); - }).fail(function () { - console.error("test failed"); + console.log("Test took ", (performance.now() - startTime) / 1000, " seconds"); + }).catch(function () { + console.log("Test took ", (performance.now() - startTime) / 1000, " seconds"); + console.error("Error !"); }); } @@ -86,18 +86,18 @@ * 3 - clicking on each menu * 3.1 - clicking on each view * @param {DomElement} element: the App menu item - * @returns {Deferred} + * @returns {Promise} */ function testApp(element){ console.log("Testing app menu:", element.dataset.menuXmlid); - if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Another infinite loop protection + if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return Promise.resolve(); // Another infinite loop protection testedApps.push(element.dataset.menuXmlid); return testMenuItem(element).then(function () { var $subMenuItems; $subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4'); - var testMenuDef = $.when(); - testMenuDef = chainDeferred($subMenuItems, testMenuDef, testMenuItem); - return testMenuDef; + var testMenuPromise = Promise.resolve(); + testMenuPromise = chainDeferred($subMenuItems, testMenuPromise, testMenuItem); + return testMenuPromise; }).then(function(){ // no effect in community var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th"); @@ -112,10 +112,10 @@ * 2 - Orchestrate the view switch * * @param {DomElement} element: the menu item - * @returns {Deferred} + * @returns {Promise} */ function testMenuItem(element){ - if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Avoid infinite loop + if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return Promise.resolve(); // Avoid infinite loop console.log("Testing menu", element.innerText.trim(), " ", element.dataset.menuXmlid); testedMenus.push(element.dataset.menuXmlid); var startActionCount = clientActionCount; @@ -128,7 +128,7 @@ $modal.modal('hide'); isModal = true; return true; - }; + } return startActionCount != clientActionCount; }).then(function() { if (!isModal) { @@ -138,7 +138,7 @@ if (!isModal) { return testViews(); } - }).fail(function() { + }).catch(function () { console.error("Error while testing", element); }); }; @@ -151,21 +151,21 @@ */ function testViews() { var $switches = $("nav.o_cp_switch_buttons > button:not(.active):visible"); - var testSwitchDef = $.when(); - // chainDeferred($switches, testSwitchDef, testViewSwitch # FIXME + var testSwitchPromise = Promise.resolve(); + // chainDeferred($switches, testSwitchPromise, testViewSwitch # FIXME _.each($switches, function(switchButton) { - testSwitchDef = testSwitchDef.then(function () { + testSwitchPromise = testSwitchPromise.then(function () { // get the view view-type data attribute return testViewSwitch(switchButton.dataset.viewType); }); }); - return testSwitchDef; + return testSwitchPromise; } /** * Test a view button * @param {string} viewType: a string for the view type to test (list, kanban ...) - * @returns {Deferred} a deferred that wait for the view to be loaded + * @returns {Promise} a promise that wait for the view to be loaded */ function testViewSwitch(viewType){ console.log("Testing view switch: ", viewType); @@ -188,20 +188,20 @@ * Click on each filter in the control pannel */ function testFilters() { - var filterDef = $.when(); + var filterProm = Promise.resolve(); // var $filters = $('div.o_control_panel div.btn-group.o_dropdown > ul.o_filters_menu > li:not(.o_add_custom_filter)'); var $filters = $('.o_filters_menu > .o_menu_item'); console.log("Testing " + $filters.length + " filters"); var filter_ids = _.compact(_.map($filters, function(f) { return f.dataset.id})); _.each(filter_ids, function(filter_id){ - filterDef = filterDef.then(function(){ + filterProm = filterProm.then(function(){ var currentViewCount = viewUpdateCount; var $filter = $('.o_menu_item[data-id="' + filter_id + '"] a'); // with some customized search views, the filter cannot be found if ($filter[0] === undefined) { console.warn('Filter with ID ', filter_id , 'cannot be found'); - return $.Deferred().resolve(); - }; + return Promise.resolve(); + } console.log('Clicking on filter "', $filter.text().trim(), '"'); $filter[0].click(); setTimeout(function() { @@ -220,52 +220,55 @@ }); }); }); - return filterDef; + return filterProm; } // utility functions /** * Wait a certain amount of time for a condition to occur - * @param stopCondition: a function that returns a boolean - * @returns {Deferred} that is rejected if the timeout is exceeded + * @param {function} stopCondition a function that returns a boolean + * @returns {Promise} that is rejected if the timeout is exceeded */ function waitForCondition(stopCondition) { - var def = $.Deferred(); - var interval = 250; - var timeLimit = 15000; - function checkCondition() { - if (stopCondition()) { - def.resolve(); - } else { - timeLimit -= interval; - if (timeLimit > 0) { - // recursive call until the resolve or the timeout - setTimeout(checkCondition, interval); + var prom = new Promise(function (resolve, reject) { + var interval = 250; + var timeLimit = 15000; + + function checkCondition() { + if (stopCondition()) { + resolve(); } else { - console.error("Timeout exceeded", stopCondition); - def.reject(); + timeLimit -= interval; + if (timeLimit > 0) { + // recursive call until the resolve or the timeout + setTimeout(checkCondition, interval); + } else { + console.error("Timeout exceeded", stopCondition); + reject(); + } } } - } - setTimeout(checkCondition, interval); - return def; - }; + setTimeout(checkCondition, interval); + }); + return prom; + } /** - * chain deferred actions - * @param $elements: a list of jquery elements to be passed as arg to the function - * @param deferred: the deferred on which other deferreds will be chained - * @param f: the function to be deferred - * @returns : the chained deferred + * Chain deferred actions. + * + * @param {jQueryElement} $elements a list of jquery elements to be passed as arg to the function + * @param {Promise} promise the promise on which other promises will be chained + * @param {function} f the function to be deferred + * @returns {Promise} the chained promise */ - function chainDeferred($elements, deferred, f) { + function chainDeferred($elements, promise, f) { _.each($elements, function(el) { - deferred = deferred.then(function () { + promise = promise.then(function () { return f(el); }); }); - return deferred; + return promise; } |
