From 3be9ae5672f76f85cd747f57539e7bc2919850f8 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Fri, 22 Nov 2019 10:49:57 +0000 Subject: [PATCH] [IMP] web_editor, *: automatically update snippet option UI on focus * website, website_blog, website_sale Some snippet option already needed to implement this by themself and now that they use custom widget, this is even more needed. The long-term goal is that developers should not have to override the onFocus method or the updateUI method but only the _computeWidgetState method. Part of https://github.com/odoo/odoo/pull/40282 In preparation of task-2122935 and other tasks --- .../static/src/js/editor/snippets.editor.js | 1 + .../static/src/js/editor/snippets.options.js | 158 +++++++++--------- .../static/src/js/editor/snippets.options.js | 123 ++++++++------ .../static/src/js/s_latest_posts_editor.js | 1 - .../static/src/js/website_blog.editor.js | 2 +- .../static/src/js/website_sale.editor.js | 37 ++-- 6 files changed, 170 insertions(+), 152 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 693eaf4001b..ca9615f6e0f 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -319,6 +319,7 @@ var SnippetEditor = Widget.extend({ var styles = _.values(editor.styles); _.sortBy(styles, '__order').forEach(style => { if (show) { + style.updateUI(); style.onFocus(); } else { style.onBlur(); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 6da88c2247a..81591af81a2 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -828,7 +828,7 @@ const MultiUserValueWidget = UserValueWidget.extend({ }, }); -const ColorpickerUserValueWidget = SelectUserValueWidget.extend({ // FIXME should be reloaded on focus +const ColorpickerUserValueWidget = SelectUserValueWidget.extend({ custom_events: { 'color_picked': '_onColorPicked', 'color_hover': '_onColorHovered', @@ -1053,16 +1053,6 @@ const SnippetOptionWidget = Widget.extend({ this.el.appendChild(this.uiFragment); this.uiFragment = null; }, - /** - * Called when the option is initialized (i.e. the parent edition overlay is - * shown for the first time). - * - * @override - */ - start: async function () { - await this._updateUI(); - return this._super.apply(this, arguments); - }, /** * Called when the parent edition overlay is covering the associated snippet * (the first time, this follows the call to the @see start method). @@ -1308,9 +1298,38 @@ const SnippetOptionWidget = Widget.extend({ */ setTarget: async function ($target) { this.$target = $target; - await this._updateUI(); + await this.updateUI(); this.$target.trigger('snippet-option-change', [this]); }, + /** + * Updates the UI. For widget update, @see _computeWidgetState. + * + * @param {function} [callback] - used to filter widgets to update + * @returns {Promise} + */ + updateUI: async function (callback) { + // For each widget, for each of their option method, notify to the + // widget the current value they should hold according to the $target's + // current state, related for that method. + const proms = this._userValueWidgets.map(async widget => { + if (callback && !callback(widget)) { + return; + } + + const methodsNames = widget.getMethodsNames(); + const proms = methodsNames.map(async methodName => { + const value = await this._computeWidgetState(methodName, widget.getMethodsParams(methodName)); + const normalizedValue = this._normalizeWidgetValue(value); + widget.setValue(normalizedValue, methodName); + }); + await Promise.all(proms); + + // Refresh the UI of all widgets (after all the current values they + // hold have been updated). + return widget.updateUI(); + }); + return Promise.all(proms); + }, //-------------------------------------------------------------------------- // Private @@ -1467,7 +1486,6 @@ const SnippetOptionWidget = Widget.extend({ return uiFragment; }, - /** * @private * @param {HTMLElement} parentEl @@ -1513,7 +1531,7 @@ const SnippetOptionWidget = Widget.extend({ return this._renderOriginalXML($xml).then(uiFragment => { this.$el.append(uiFragment); - return this._updateUI(); + return this.updateUI(); }); }, /** @@ -1544,42 +1562,13 @@ const SnippetOptionWidget = Widget.extend({ }); if (!previewMode) { - await this._updateUI(w => !w.isPreviewed() || w === widget); + await this.updateUI(w => !w.isPreviewed() || w === widget); } else { - await this._updateUI(w => w !== widget && !w.isPreviewed()); + await this.updateUI(w => w !== widget && !w.isPreviewed()); } this.$target.trigger('content_changed'); }, - /** - * Updates the UI. For widget update, @see _computeWidgetState. - * - * @param {function} [callback] - used to filter widgets to update - * @returns {Promise} - */ - _updateUI: async function (callback) { - // For each widget, for each of their option method, notify to the - // widget the current value they should hold according to the $target's - // current state, related for that method. - const proms = this._userValueWidgets.map(async widget => { - if (callback && !callback(widget)) { - return; - } - - const methodsNames = widget.getMethodsNames(); - const proms = methodsNames.map(async methodName => { - const value = await this._computeWidgetState(methodName, widget.getMethodsParams(methodName)); - const normalizedValue = this._normalizeWidgetValue(value); - widget.setValue(normalizedValue, methodName); - }); - await Promise.all(proms); - - // Refresh the UI of all widgets (after all the current values they - // hold have been updated). - return widget.updateUI(); - }); - return Promise.all(proms); - }, //-------------------------------------------------------------------------- // Handlers @@ -1748,11 +1737,6 @@ registry.sizing = SnippetOptionWidget.extend({ * @override */ onFocus: function () { - var resizeValues = this._getSize(); - _.each(resizeValues, (value, key) => { - this.$handles.filter('.' + key).toggleClass('readonly', !value); - }); - this._onResize(); }, /** @@ -1773,6 +1757,16 @@ registry.sizing = SnippetOptionWidget.extend({ await this._super(...arguments); this._onResize(); }, + /** + * @override + */ + updateUI: function () { + this._super(...arguments); + const resizeValues = this._getSize(); + _.each(resizeValues, (value, key) => { + this.$handles.filter('.' + key).toggleClass('readonly', !value); + }); + }, //-------------------------------------------------------------------------- // Private @@ -1968,6 +1962,20 @@ registry.background = SnippetOptionWidget.extend({ this.bindBackgroundEvents(); this.__customImageSrc = this._getSrcFromCssValue(); }, + /** + * @override + */ + updateUI: async function () { + await this._super(...arguments); + var src = this._getSrcFromCssValue(); + this.removeBgWidget.el.classList.toggle('d-none', !src); + if (src) { + var split = src.split('/'); + this.editBgTextEl.textContent = split[split.length - 1]; + } else { + this.editBgTextEl.textContent = this._getDefaultTextContent(); + } + }, //-------------------------------------------------------------------------- // Private @@ -2026,20 +2034,6 @@ registry.background = SnippetOptionWidget.extend({ var srcValueWrapper = /url\(['"]*|['"]*\)|^none$/g; return value && value.replace(srcValueWrapper, '') || ''; }, - /** - * @override - */ - _updateUI: async function () { - await this._super.apply(this, arguments); - var src = this._getSrcFromCssValue(); - this.removeBgWidget.el.classList.toggle('d-none', !src); - if (src) { - var split = src.split('/'); - this.editBgTextEl.textContent = split[split.length - 1]; - } else { - this.editBgTextEl.textContent = this._getDefaultTextContent(); - } - }, /** * @override */ @@ -2082,7 +2076,7 @@ registry.background = SnippetOptionWidget.extend({ this.__customImageSrc = value; this.background(false, this.__customImageSrc, {}); this.$target.toggleClass('oe_custom_bg', !!value); - await this._updateUI(); + await this.updateUI(); this.$target.trigger('snippet-option-change', [this]); }, @@ -2142,7 +2136,7 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({ this.$target.on('snippet-option-change', () => { // Hides option if the bg image is removed in favor of a bg color - this._updateUI(); + this.updateUI(); // this.img is used to compute dragging speed this.img.src = this._getSrcFromCssValue(); }); @@ -2160,12 +2154,6 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({ $(window).off('.bgposition'); this._super.apply(this, arguments); }, - /** - * @override - */ - onFocus: function () { - this._updateUI(); - }, //-------------------------------------------------------------------------- // Options @@ -2211,6 +2199,21 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({ this._super(...arguments); }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + /** + * Disables background position if no background image, disables size inputs + * in cover mode. + * + * @override + */ + updateUI: async function () { + await this._super(...arguments); + this.$el.toggleClass('d-none', this.$target.css('background-image') === 'none'); + this.$el.find('we-input').toggleClass('d-none', this.$target.css('background-repeat') !== 'repeat'); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -2317,17 +2320,6 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({ // This is caused by the click event which we are currently handling bubbling up to the document. window.setTimeout(() => $(document).on('click.bgposition', this._onDocumentClicked.bind(this)), 0); }, - /** - * Disables background position if no background image, disables size inputs - * in cover mode. - * - * @override - */ - _updateUI: async function () { - await this._super.apply(this, arguments); - this.$el.toggleClass('d-none', this.$target.css('background-image') === 'none'); - this.$el.find('we-input').toggleClass('d-none', this.$target.css('background-repeat') !== 'repeat'); - }, /** * Returns the src value from a css value related to a background image * (e.g. "url('blabla')" => "blabla" / "none" => ""). diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index d38744d3192..e13a6715c14 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -107,7 +107,7 @@ options.registry.background.include({ delete target.dataset.bgVideoSrc; } this._refreshPublicWidgets(); - await this._updateUI(); + await this.updateUI(); }, /** * Returns whether the current target has a background video or not. @@ -350,12 +350,18 @@ options.registry.CarouselItem = options.Class.extend({ this._super(...arguments); this.$carousel.off('.carousel_item_option'); }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + /** * Updates the slide counter. * * @override */ - onFocus: function () { + updateUI: function () { + this._super(...arguments); const $items = this.$carousel.find('.carousel-item'); const $activeSlide = $items.filter('.active'); const updatedText = ` (${$activeSlide.index() + 1}/${$items.length})`; @@ -483,11 +489,23 @@ options.registry.navTabs = options.Class.extend({ $activeLink.parent().remove(); $activePane.remove(); self._findLinksAndPanes(); - self._updateUI(); // TODO forced to do this because we do not return deferred for options + self.updateUI(); // TODO forced to do this because we do not return deferred for options }); $next.tab('show'); }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + updateUI: async function () { + await this._super(...arguments); + this.$el.filter('[data-remove-tab]').toggleClass('d-none', this.$tabPanes.length <= 2); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -522,14 +540,6 @@ options.registry.navTabs = options.Class.extend({ }); } }, - /** - * @private - * @override - */ - _updateUI: async function () { - await this._super.apply(this, arguments); - this.$el.filter('[data-remove-tab]').toggleClass('d-none', this.$tabPanes.length <= 2); - }, }); options.registry.sizing_x = options.registry.sizing.extend({ @@ -1120,7 +1130,7 @@ options.registry.gallery = options.Class.extend({ } this.mode('reset', this.getMode()); this.trigger_up('cover_update'); - this._updateUI(); + this.updateUI(); }); dialog.open(); }, @@ -1352,6 +1362,21 @@ options.registry.gallery = options.Class.extend({ this.mode('reset', this.getMode()); } }, + /** + * @override + */ + updateUI: async function () { + await this._super(...arguments); + + this.$el.find('[data-interval]').closest('we-select')[0] + .classList.toggle('d-none', this.activeMode !== 'slideshow'); + + this.$el.find('[data-columns]').closest('we-select')[0] + .classList.toggle('d-none', !(this.activeMode === 'grid' || this.activeMode === 'masonry')); + + this.el.querySelector('.o_w_image_spacing_option') + .classList.toggle('d-none', this.activeMode === 'slideshow'); + }, //-------------------------------------------------------------------------- // Private @@ -1440,21 +1465,6 @@ options.registry.gallery = options.Class.extend({ $container.empty().append($content); return $container; }, - /** - * @override - */ - _updateUI: async function () { - await this._super(...arguments); - - this.$el.find('[data-interval]').closest('we-select')[0] - .classList.toggle('d-none', this.activeMode !== 'slideshow'); - - this.$el.find('[data-columns]').closest('we-select')[0] - .classList.toggle('d-none', !(this.activeMode === 'grid' || this.activeMode === 'masonry')); - - this.el.querySelector('.o_w_image_spacing_option') - .classList.toggle('d-none', this.activeMode === 'slideshow'); - }, }); options.registry.gallery_img = options.Class.extend({ @@ -1553,10 +1563,16 @@ options.registry.topMenuColor = options.Class.extend({ }); return def; }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + /** * @override */ - onFocus: function () { + updateUI: function () { + this._super(...arguments); this.trigger_up('action_demand', { actionName: 'get_page_option', params: ['header_overlay'], @@ -1779,7 +1795,7 @@ options.registry.CoverProperties = options.Class.extend({ if (!this.$target.hasClass('o_record_has_cover')) { this.$el.find('.o_record_cover_opt_size_default[data-select-class]').click(); } - this._updateUI(); + this.updateUI(); }); }, /** @@ -1804,33 +1820,13 @@ options.registry.CoverProperties = options.Class.extend({ }, //-------------------------------------------------------------------------- - // Private + // Public //-------------------------------------------------------------------------- - /** - * @override - */ - _computeWidgetState: function (methodName, params) { - switch (methodName) { - case 'filterValue': { - return parseFloat(this.$filter.css('opacity')).toFixed(1); - } - case 'filterColor': { - const classes = this.filterColorClasses.split(' '); - for (const className of classes) { - if (this.$filter.hasClass(className)) { - return className; - } - } - return ''; - } - } - return this._super(...arguments); - }, /** * @private */ - _updateUI: async function () { + updateUI: async function () { await this._super(...arguments); // Only show options which are useful to the current cover @@ -1859,6 +1855,31 @@ options.registry.CoverProperties = options.Class.extend({ this.$target[0].dataset.filterValue = this.$filterValueOpts.filter('.active').data('filterValue') || 0.0; this.$target[0].dataset.filterColor = this.$filterColorOpts.filter('.active').data('filterColor') || ''; }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _computeWidgetState: function (methodName, params) { + switch (methodName) { + case 'filterValue': { + return parseFloat(this.$filter.css('opacity')).toFixed(1); + } + case 'filterColor': { + const classes = this.filterColorClasses.split(' '); + for (const className of classes) { + if (this.$filter.hasClass(className)) { + return className; + } + } + return ''; + } + } + return this._super(...arguments); + }, }); /** diff --git a/addons/website_blog/static/src/js/s_latest_posts_editor.js b/addons/website_blog/static/src/js/s_latest_posts_editor.js index af6235f2e98..e70c4041e6d 100644 --- a/addons/website_blog/static/src/js/s_latest_posts_editor.js +++ b/addons/website_blog/static/src/js/s_latest_posts_editor.js @@ -26,7 +26,6 @@ sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({ el.textContent = blog.name; menuEl.appendChild(el); } - return this._updateUI(); }); }, }); diff --git a/addons/website_blog/static/src/js/website_blog.editor.js b/addons/website_blog/static/src/js/website_blog.editor.js index a066451e764..2fdc791cbc9 100644 --- a/addons/website_blog/static/src/js/website_blog.editor.js +++ b/addons/website_blog/static/src/js/website_blog.editor.js @@ -111,7 +111,7 @@ options.registry.CoverProperties.include({ /** * @override */ - _updateUI: async function () { + updateUI: async function () { await this._super(...arguments); var isRegularCover = this.$target.is('.o_wblog_post_page_cover_regular'); var $coverFull = this.$el.find('[data-select-class*="cover_full"]'); diff --git a/addons/website_sale/static/src/js/website_sale.editor.js b/addons/website_sale/static/src/js/website_sale.editor.js index bbcca2baf74..7e52f53dc04 100644 --- a/addons/website_sale/static/src/js/website_sale.editor.js +++ b/addons/website_sale/static/src/js/website_sale.editor.js @@ -214,6 +214,27 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({ }).then(reload); }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + updateUI: async function () { + await this._super.apply(this, arguments); + + var sizeX = parseInt(this.$target.attr('colspan') || 1); + var sizeY = parseInt(this.$target.attr('rowspan') || 1); + + var $size = this.$el.find('.o_wsale_soptions_menu_sizes'); + $size.find('tr:nth-child(-n + ' + sizeY + ') td:nth-child(-n + ' + sizeX + ')') + .addClass('selected'); + + // Adapt size array preview to fit ppr + $size.find('tr td:nth-child(n + ' + parseInt(this.ppr + 1) + ')').hide(); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -244,22 +265,6 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({ } }); }, - /** - * @override - */ - _updateUI: async function () { - await this._super.apply(this, arguments); - - var sizeX = parseInt(this.$target.attr('colspan') || 1); - var sizeY = parseInt(this.$target.attr('rowspan') || 1); - - var $size = this.$el.find('.o_wsale_soptions_menu_sizes'); - $size.find('tr:nth-child(-n + ' + sizeY + ') td:nth-child(-n + ' + sizeX + ')') - .addClass('selected'); - - // Adapt size array preview to fit ppr - $size.find('tr td:nth-child(n + ' + parseInt(this.ppr + 1) + ')').hide(); - }, //-------------------------------------------------------------------------- // Handlers