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 6fb1f5ebee8..bf4dd804d1d 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -321,62 +321,6 @@ var SnippetEditor = Widget.extend({ // Private //-------------------------------------------------------------------------- - /** - * Transforms option UI description into actual DOM. - * - * @private - * @param {jQuery} $el - */ - _createOptionUI: function ($el) { - const $optionSection = $(core.qweb.render('web_editor.customize_block_option')); - $optionSection.append($el); - const uiEl = $optionSection[0]; - - // Build group first as their internal components will be built after - uiEl.querySelectorAll('we-row').forEach(el => { - const infos = this._extraInfoFromDescriptionElement(el); - const groupEl = options.Class.prototype.buildRowElement(infos.title, infos.options); - el.parentNode.insertBefore(groupEl, el); - el.parentNode.removeChild(el); - }); - - // Build standard components - uiEl.querySelectorAll('we-select').forEach(el => { - const infos = this._extraInfoFromDescriptionElement(el); - const selectEl = options.Class.prototype.buildSelectElement(infos.title, infos.options); - el.parentNode.insertBefore(selectEl, el); - el.parentNode.removeChild(el); - }); - uiEl.querySelectorAll('we-checkbox').forEach(el => { - const infos = this._extraInfoFromDescriptionElement(el); - const checkboxEl = options.Class.prototype.buildCheckboxElement(infos.title, infos.options); - el.parentNode.insertBefore(checkboxEl, el); - el.parentNode.removeChild(el); - }); - uiEl.querySelectorAll('we-input').forEach(el => { - const infos = this._extraInfoFromDescriptionElement(el); - const inputEl = options.Class.prototype.buildInputElement(infos.title, infos.options); - el.parentNode.insertBefore(inputEl, el); - el.parentNode.removeChild(el); - }); - - return $optionSection; - }, - /** - * @private - * @param {HTMLElement} el - * @returns {Object} - */ - _extraInfoFromDescriptionElement: function (el) { - return { - title: el.getAttribute('string'), - options: { - classes: el.classList, - dataAttributes: el.dataset, - childNodes: [...el.childNodes], - }, - }; - }, /** * DOMElements have a default name which appears in the overlay when they * are being edited. This method retrieves this name; it can be defined @@ -418,7 +362,8 @@ var SnippetEditor = Widget.extend({ var $optionsSection = $(core.qweb.render('web_editor.customize_block_options_section', { name: this._getName(), })).data('editor', this); - $optionsSection.find('we-button-group').contents().each((i, node) => { + const $optionsSectionBtnGroup = $optionsSection.find('we-button-group'); + $optionsSectionBtnGroup.contents().each((i, node) => { if (node.nodeType === Node.TEXT_NODE) { node.parentNode.removeChild(node); } @@ -447,13 +392,15 @@ var SnippetEditor = Widget.extend({ } var optionName = val.option; - - var $ui = val.$el.children().clone(true); var option = new (options.registry[optionName] || options.Class)( this, + val.$el.children(), val.base_target ? this.$target.find(val.base_target).eq(0) : this.$target, this.$el, - _.extend({snippetName: this._getName()}, val.data), + _.extend({ + optionName: optionName, + snippetName: this._getName(), + }, val.data), this.options ); var key = optionName || _.uniqueId('option'); @@ -465,8 +412,7 @@ var SnippetEditor = Widget.extend({ } this.styles[key] = option; option.__order = i++; - const $option = this._createOptionUI($ui).addClass('snippet-option-' + optionName); - return option.attachTo($option); + return option.appendTo(document.createDocumentFragment()); }); this.isTargetMovable = (this.selectorSiblings.length > 0 || this.selectorChildren.length > 0); @@ -476,8 +422,8 @@ var SnippetEditor = Widget.extend({ return Promise.all(defs).then(() => { const options = _.sortBy(this.styles, '__order'); options.forEach(option => { - if (option.isTopOption()) { - $optionsSection.find('we-button-group').first().prepend(option.$el); + if (option.isTopOption) { + $optionsSectionBtnGroup.prepend(option.$el); } else { $optionsSection.append(option.$el); } 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 509fa04089f..794a733ceef 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -59,6 +59,7 @@ function _computePxByRem(toRem) { * referenced thanks to the data-js key in the web_editor options template. */ var SnippetOption = Widget.extend({ + tagName: 'we-customizeblock-option', events: { 'mouseenter we-button': '_onOptionPreview', 'click we-button': '_onOptionSelection', @@ -69,6 +70,13 @@ var SnippetOption = Widget.extend({ 'click we-input': '_onOptionInputClick', 'keydown we-input input': '_onOptionInputKeydown', }, + /** + * Indicates if the option should be displayed in the button group at the + * top of the options panel, next to the clone/remove button. + * + * @type {boolean} + */ + isTopOption: false, /** * The option `$el` is supposed to be the associated DOM UI element. @@ -78,15 +86,63 @@ var SnippetOption = Widget.extend({ * * @constructor */ - init: function (parent, $target, $overlay, data, options) { + init: function (parent, $uiElements, $target, $overlay, data, options) { this._super.apply(this, arguments); + + this.uiFragment = document.createDocumentFragment(); + $uiElements.clone(true).appendTo(this.uiFragment); + this.$target = $target; this.$overlay = $overlay; this.data = data; this.options = options; + + this.className = 'snippet-option-' + this.data.optionName; + this.ownerDocument = this.$target[0].ownerDocument; this.__methodNames = []; }, + /** + * @override + */ + willStart: function () { + // Build group first as their internal components will be built after + this.uiFragment.querySelectorAll('we-row').forEach(el => { + const infos = this._extraInfoFromDescriptionElement(el); + const groupEl = this._buildRowElement(infos.title, infos.options); + el.parentNode.insertBefore(groupEl, el); + el.parentNode.removeChild(el); + }); + + // Build standard components + this.uiFragment.querySelectorAll('we-select').forEach(el => { + const infos = this._extraInfoFromDescriptionElement(el); + const selectEl = this._buildSelectElement(infos.title, infos.options); + el.parentNode.insertBefore(selectEl, el); + el.parentNode.removeChild(el); + }); + this.uiFragment.querySelectorAll('we-checkbox').forEach(el => { + const infos = this._extraInfoFromDescriptionElement(el); + const checkboxEl = this._buildCheckboxElement(infos.title, infos.options); + el.parentNode.insertBefore(checkboxEl, el); + el.parentNode.removeChild(el); + }); + this.uiFragment.querySelectorAll('we-input').forEach(el => { + const infos = this._extraInfoFromDescriptionElement(el); + const inputEl = this._buildInputElement(infos.title, infos.options); + el.parentNode.insertBefore(inputEl, el); + el.parentNode.removeChild(el); + }); + + return this._super(...arguments); + }, + /** + * @override + */ + renderElement: function () { + this._super(...arguments); + this.el.appendChild(this.uiFragment); + }, /** * Called when the option is initialized (i.e. the parent edition overlay is * shown for the first time). @@ -97,15 +153,6 @@ var SnippetOption = Widget.extend({ this._updateUI(); return this._super.apply(this, arguments); }, - /** - * Indicates if the option should be displayed in the button group at the - * top of the options panel, next to the clone/remove button. - * - * @returns {boolean} - */ - isTopOption: function () { - return false; - }, /** * Called when the parent edition overlay is covering the associated snippet * (the first time, this follows the call to the @see start method). @@ -382,6 +429,148 @@ var SnippetOption = Widget.extend({ // Private //-------------------------------------------------------------------------- + /** + * Build the correct DOM for an element according to the given options. + * + * @private + * @param {string} tagName + * @param {string} [title] + * @param {Object} [options] + * @param {string[]} [options.classes] + * @param {Object} [options.dataAttributes] + * @returns {HTMLElement} + */ + _buildElement: function (tagName, title, options) { + const el = document.createElement(tagName); + + if (title) { + const titleEl = this._buildTitleElement(title); + el.appendChild(titleEl); + } + + if (options && options.classes) { + el.classList.add(...options.classes); + } + if (options && options.dataAttributes) { + for (const key in options.dataAttributes) { + el.dataset[key] = options.dataAttributes[key]; + } + } + + return el; + }, + /** + * Build the correct DOM for a we-checkbox element. + * + * @private + * @param {string} [title] + * @param {Object} [options] - @see this._buildElement + * @returns {HTMLElement} + */ + _buildCheckboxElement: function (title, options) { + const buttonEl = this._buildElement('we-button', title, options); + buttonEl.classList.add('o_we_checkbox_wrapper'); + + const checkboxEl = document.createElement('we-checkbox'); + buttonEl.appendChild(checkboxEl); + + return buttonEl; + }, + /** + * Build the correct DOM for a we-row element. + * + * @private + * @param {string} [title] + * @param {Object} [options] - @see this._buildElement + * @param {HTMLElement[]} [options.childNodes] + * @returns {HTMLElement} + */ + _buildRowElement: function (title, options) { + const groupEl = this._buildElement('we-row', title, options); + + const rowEl = document.createElement('div'); + groupEl.appendChild(rowEl); + + if (options && options.childNodes) { + options.childNodes.forEach(node => rowEl.appendChild(node)); + } + + return groupEl; + }, + /** + * Build the correct DOM for a we-input element. + * + * @private + * @param {string} [title] + * @param {Object} [options] - @see this._buildElement + */ + _buildInputElement: function (title, options) { + const inputWrapperEl = this._buildElement('we-input', title, options); + + var unit = inputWrapperEl.dataset.unit; + if (unit === undefined) { + unit = 'px'; + } + inputWrapperEl.dataset.unit = unit; + if (inputWrapperEl.dataset.saveUnit === undefined) { + inputWrapperEl.dataset.saveUnit = unit; + } + + var defaultValue = inputWrapperEl.dataset.defaultValue; + if (defaultValue === undefined) { + defaultValue = ('0' + unit); + } + inputWrapperEl.dataset.defaultValue = defaultValue; + + var inputEl = document.createElement('input'); + inputEl.setAttribute('type', 'text'); + inputEl.setAttribute('placeholder', defaultValue.replace(unit, '')); + inputWrapperEl.appendChild(inputEl); + + var unitEl = document.createElement('span'); + unitEl.textContent = unit; + inputWrapperEl.appendChild(unitEl); + + return inputWrapperEl; + }, + /** + * Build the correct DOM for a we-select element. + * + * @private + * @param {string} [title] + * @param {Object} [options] - @see this._buildElement + * @param {HTMLElement[]} [options.childNodes] + * @param {HTMLElement} [options.valueEl] + * @returns {HTMLElement} + */ + _buildSelectElement: function (title, options) { + const selectEl = this._buildElement('we-select', title, options); + + if (options && options.valueEl) { + selectEl.appendChild(options.valueEl); + } + + const menuTogglerEl = document.createElement('we-toggler'); + selectEl.appendChild(menuTogglerEl); + + var menuEl = document.createElement('we-select-menu'); + if (options && options.childNodes) { + options.childNodes.forEach(node => menuEl.appendChild(node)); + } + selectEl.appendChild(menuEl); + + return selectEl; + }, + /** + * @private + * @param {string} title + * @returns {HTMLElement} + */ + _buildTitleElement: function (title) { + const titleEl = document.createElement('we-title'); + titleEl.textContent = title; + return titleEl; + }, /** * Converts the given (value + unit) string to a numeric value expressed in * the given css unit. @@ -424,6 +613,21 @@ var SnippetOption = Widget.extend({ } return value * converter(cssProp, this.$target); }, + /** + * @statis + * @param {HTMLElement} el + * @returns {Object} + */ + _extraInfoFromDescriptionElement: function (el) { + return { + title: el.getAttribute('string'), + options: { + classes: el.classList, + dataAttributes: el.dataset, + childNodes: [...el.childNodes], + }, + }; + }, /** * Reactivate the options that were activated before previews. */ @@ -778,153 +982,6 @@ var SnippetOption = Widget.extend({ } this._reset(); }, - - //-------------------------------------------------------------------------- - // Static - //-------------------------------------------------------------------------- - - /** - * Build the correct DOM for an element according to the given options. - * - * @static - * @param {string} tagName - * @param {string} [title] - * @param {Object} [options] - * @param {string[]} [options.classes] - * @param {Object} [options.dataAttributes] - * @returns {HTMLElement} - */ - buildElement: function (tagName, title, options) { - const el = document.createElement(tagName); - - if (title) { - const titleEl = SnippetOption.prototype.buildTitleElement(title); - el.appendChild(titleEl); - } - - if (options && options.classes) { - el.classList.add(...options.classes); - } - if (options && options.dataAttributes) { - for (const key in options.dataAttributes) { - el.dataset[key] = options.dataAttributes[key]; - } - } - - return el; - }, - /** - * Build the correct DOM for a we-checkbox element. - * - * @static - * @param {string} [title] - * @param {Object} [options] - @see this.buildElement - * @returns {HTMLElement} - */ - buildCheckboxElement: function (title, options) { - const buttonEl = SnippetOption.prototype.buildElement('we-button', title, options); - buttonEl.classList.add('o_we_checkbox_wrapper'); - - const checkboxEl = document.createElement('we-checkbox'); - buttonEl.appendChild(checkboxEl); - - return buttonEl; - }, - /** - * Build the correct DOM for a we-row element. - * - * @static - * @param {string} [title] - * @param {Object} [options] - @see this.buildElement - * @param {HTMLElement[]} [options.childNodes] - * @returns {HTMLElement} - */ - buildRowElement: function (title, options) { - const groupEl = SnippetOption.prototype.buildElement('we-row', title, options); - - const rowEl = document.createElement('div'); - groupEl.appendChild(rowEl); - - if (options && options.childNodes) { - options.childNodes.forEach(node => rowEl.appendChild(node)); - } - - return groupEl; - }, - /** - * Build the correct DOM for a we-input element. - * - * @static - * @param {string} [title] - * @param {Object} [options] - @see this.buildElement - */ - buildInputElement: function (title, options) { - const inputWrapperEl = SnippetOption.prototype.buildElement('we-input', title, options); - - var unit = inputWrapperEl.dataset.unit; - if (unit === undefined) { - unit = 'px'; - } - inputWrapperEl.dataset.unit = unit; - if (inputWrapperEl.dataset.saveUnit === undefined) { - inputWrapperEl.dataset.saveUnit = unit; - } - - var defaultValue = inputWrapperEl.dataset.defaultValue; - if (defaultValue === undefined) { - defaultValue = ('0' + unit); - } - inputWrapperEl.dataset.defaultValue = defaultValue; - - var inputEl = document.createElement('input'); - inputEl.setAttribute('type', 'text'); - inputEl.setAttribute('placeholder', defaultValue.replace(unit, '')); - inputWrapperEl.appendChild(inputEl); - - var unitEl = document.createElement('span'); - unitEl.textContent = unit; - inputWrapperEl.appendChild(unitEl); - - return inputWrapperEl; - }, - /** - * Build the correct DOM for a we-select element. - * - * @static - * @param {string} [title] - * @param {Object} [options] - @see this.buildElement - * @param {HTMLElement[]} [options.childNodes] - * @param {HTMLElement} [options.valueEl] - * @returns {HTMLElement} - */ - buildSelectElement: function (title, options) { - const selectEl = SnippetOption.prototype.buildElement('we-select', title, options); - - if (options && options.valueEl) { - selectEl.appendChild(options.valueEl); - } - - const menuTogglerEl = document.createElement('we-toggler'); - selectEl.appendChild(menuTogglerEl); - - var menuEl = document.createElement('we-select-menu'); - if (options && options.childNodes) { - options.childNodes.forEach(node => menuEl.appendChild(node)); - } - selectEl.appendChild(menuEl); - - return selectEl; - }, - /** - * @static - * @param {string} title - * @returns {HTMLElement} - */ - buildTitleElement: function (title) { - const titleEl = document.createElement('we-title'); - titleEl.textContent = title; - return titleEl; - }, }); //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -1219,7 +1276,7 @@ registry.colorpicker = SnippetOption.extend({ // Build the select element with a custom span to hold the color preview this.colorPreviewEl = document.createElement('span'); - const selectEl = this.buildSelectElement(this.data.string, { + const selectEl = this._buildSelectElement(this.data.string, { classes: ['o_we_so_color_palette'], childNodes: [this.colorPalette.el], valueEl: this.colorPreviewEl, @@ -1343,7 +1400,7 @@ registry.background = SnippetOption.extend({ this.removeBgEl.dataset.background = ''; this.removeBgEl.dataset.noPreview = 'true'; - this.$el.append(this.buildRowElement(this.data.string, { + this.$el.append(this._buildRowElement(this.data.string, { childNodes: [editBgEl, this.removeBgEl], })); diff --git a/addons/web_editor/static/src/xml/snippets.xml b/addons/web_editor/static/src/xml/snippets.xml index 658bef34353..2b0ba9f4589 100644 --- a/addons/web_editor/static/src/xml/snippets.xml +++ b/addons/web_editor/static/src/xml/snippets.xml @@ -31,9 +31,6 @@ - - -
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index 2d5747e57e3..6edbc840aa4 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -329,6 +329,8 @@ options.registry.Carousel = options.Class.extend({ }); options.registry.CarouselItem = options.Class.extend({ + isTopOption: true, + /** * @override */ @@ -353,12 +355,6 @@ options.registry.CarouselItem = options.Class.extend({ this._super(...arguments); this.$carousel.off('.carousel_item_option'); }, - /** - * @override - */ - isTopOption: function () { - return true; - }, /** * Updates the slide counter. * @@ -1597,6 +1593,7 @@ options.registry.topMenuColor = options.registry.colorpicker.extend({ */ options.registry.anchor = options.Class.extend({ xmlDependencies: ['/website/static/src/xml/website.editor.xml'], + isTopOption: true, //-------------------------------------------------------------------------- // Public @@ -1620,12 +1617,6 @@ options.registry.anchor = options.Class.extend({ return this._super.apply(this, arguments); }, - /** - * @override - */ - isTopOption: function () { - return true; - }, /** * @override */ 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 8d394bdd0b6..4f35b1bf00e 100644 --- a/addons/website_sale/static/src/js/website_sale.editor.js +++ b/addons/website_sale/static/src/js/website_sale.editor.js @@ -184,7 +184,7 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({ }).then(data => { var $menu = this.$el.find('[name="style"]'); for (var k in data) { - $menu.append(this.buildCheckboxElement(data[k]['name'], { + $menu.append(this._buildCheckboxElement(data[k]['name'], { dataAttributes: { 'style': data[k]['id'], 'toggleClass': data[k]['html_class'],