diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js index 4af811d41bc..b14684e4acd 100644 --- a/addons/web/static/src/js/core/dom.js +++ b/addons/web/static/src/js/core/dom.js @@ -343,19 +343,37 @@ var dom = { // part, the button is disabled without any visual effect. $button.addClass('o_debounce_disabled'); Promise.resolve(dom.DEBOUNCE && concurrency.delay(dom.DEBOUNCE)).then(function () { - $button.addClass('disabled').prop('disabled', true); $button.removeClass('o_debounce_disabled'); - - return Promise.resolve(result).then(function () { - $button.removeClass('disabled').prop('disabled', false); - }).guardedCatch(function () { - $button.removeClass('disabled').prop('disabled', false); - }); + const restore = dom.addButtonLoadingEffect($button[0]); + return Promise.resolve(result).then(restore).guardedCatch(restore); }); return result; }; }, + /** + * Gives the button a loading effect by disabling it and adding a `fa` + * spinner icon. + * The existing button `fa` icons will be hidden through css. + * + * @param {HTMLElement} btn - the button to disable/load + * @return {function} a callback function that will restore the button + * initial state + */ + addButtonLoadingEffect: function (btn) { + const $btn = $(btn); + $btn.addClass('o_website_btn_loading disabled'); + $btn.prop('disabled', true); + const $loader = $('', { + class: 'fa fa-refresh fa-spin mr-2', + }); + $btn.prepend($loader); + return () => { + $btn.removeClass('o_website_btn_loading disabled'); + $btn.prop('disabled', false); + $loader.remove(); + }; + }, /** * Prepends content in a jQuery object and optionnally triggers an event * diff --git a/addons/web_editor/static/src/js/common/ace.js b/addons/web_editor/static/src/js/common/ace.js index 4bbaf5f8fdd..769b4f33023 100644 --- a/addons/web_editor/static/src/js/common/ace.js +++ b/addons/web_editor/static/src/js/common/ace.js @@ -5,6 +5,7 @@ var ajax = require('web.ajax'); var config = require('web.config'); var concurrency = require('web.concurrency'); var core = require('web.core'); +var dom = require('web.dom'); var Dialog = require('web.Dialog'); var Widget = require('web.Widget'); var localStorage = require('web.local_storage'); @@ -911,8 +912,9 @@ var ViewEditor = Widget.extend({ * * @private */ - _onSaveClick: function () { - this._saveResources(); + _onSaveClick: function (ev) { + const restore = dom.addButtonLoadingEffect(ev.currentTarget); + this._saveResources().guardedCatch(restore); }, /** * Called when the user wants to switch from xml to scss or vice-versa -> diff --git a/addons/website/static/src/js/editor/editor_menu.js b/addons/website/static/src/js/editor/editor_menu.js index 7249e0d3741..e330df97001 100644 --- a/addons/website/static/src/js/editor/editor_menu.js +++ b/addons/website/static/src/js/editor/editor_menu.js @@ -2,6 +2,7 @@ odoo.define('website.editor.menu', function (require) { 'use strict'; var Dialog = require('web.Dialog'); +var dom = require('web.dom'); var Widget = require('web.Widget'); var core = require('web.core'); var Wysiwyg = require('web_editor.wysiwyg.root'); @@ -233,8 +234,9 @@ var EditorMenu = Widget.extend({ * * @private */ - _onSaveClick: function () { - this.save(); + _onSaveClick: function (ev) { + const restore = dom.addButtonLoadingEffect(ev.currentTarget); + this.save().then(restore).guardedCatch(restore); }, /** * @private diff --git a/addons/website/static/src/js/menu/navbar.js b/addons/website/static/src/js/menu/navbar.js index da86853f4e1..937392f88ec 100644 --- a/addons/website/static/src/js/menu/navbar.js +++ b/addons/website/static/src/js/menu/navbar.js @@ -180,12 +180,8 @@ var WebsiteNavbar = publicWidget.RootWidget.extend({ * @param {Event} ev */ _onActionMenuClick: function (ev) { - var $button = $(ev.currentTarget); - $button.prop('disabled', true); - var always = function () { - $button.prop('disabled', false); - }; - this._handleAction($button.data('action')).then(always).guardedCatch(always); + const restore = dom.addButtonLoadingEffect(ev.currentTarget); + this._handleAction($(ev.currentTarget).data('action')).then(restore).guardedCatch(restore); }, /** * Called when an action is asked to be executed from a child widget -> diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index 2dad9f8f87f..c14c72efe7f 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -1405,3 +1405,11 @@ $ribbon-padding: 100px; bottom: 0; } } + +.o_website_btn_loading { + cursor: wait; + opacity: $btn-disabled-opacity; + .fa:not(.fa-spin) { + display: none; + } +}