[REF] web_editor, *: use widgets for left panel components

Before this commit, the rendering of inner content of the left panel
options was handled through _buildXXXElement methods in the options
widgets. Now they are handled through sub-widgets which also handle
the user events.

Each sub-widget is notified with the string value it should hold
according to the snippet option target and methods. Each sub-widget
notifies a string value following user interactions, which snippet
options can use to adapt their target through their option methods.

The current sub-widgets introduced by this commit are (others will
follow):

1) we-button
2) we-checkbox
3) we-input
4) we-select (typically containing multiple we-button widgets)
5) we-multi (typically containing multiple we-input widget)

Following this commit only 3 standard option methods remain, which can
be used with any meaningful widget:

1) selectClass

-> Same as before + handle the old 'toggleClass' method when used with
   a checkbox-like widget.

2) selectStyle

-> Same as old setStyle, introduced recently (simply renamed)

3) selectDataAttribute

-> Handle both old selectDataAttribute and setDataAttribute, introduced
   recently

The parameters these methods received are now:
- previewMode: same as before
- widgetValue: the string value the widget currently holds
- params: additional parameters (mainly dataset of the related xml)
    + params.possibleValues (all the possible values for the method,
      meaninful for a select or a checkbox)
    + params.defaultValue (the value that the widget would give if it
      even holds no value)

When defining a method in XML, the given value is the default value.
e.g. data-select-style="34px" data-css-property="margin-top"
-> if set on any widget, allows to retrieve a value from the user to
   customize the margin-top, the default value being 34px.
   -> if used with a we-input, allow to retrieve any value and an empty
      value will be equal to 34px
   -> if used on a checkbox, allow to toggle between no value and 34px.
   -> ...

Hopefully, detailed documentation about all of this will follow. Code
documentation should already be helpful.

Last note: the _updateUI method (replacing the old _setActive) is now
async (so does all the functions calling it). In particular, the _select
method is now async.

Part of https://github.com/odoo/odoo/pull/40282
In preparation of task-2122935 and other tasks
This commit is contained in:
qsm-odoo
2019-12-01 15:49:57 +00:00
parent 75ab52af9c
commit b38253f400
16 changed files with 1673 additions and 1096 deletions
@@ -0,0 +1,144 @@
odoo.define('web_editor.utils', function (require) {
'use strict';
/**
* window.getComputedStyle cannot work properly with CSS shortcuts (like
* 'border-width' which is a shortcut for the top + right + bottom + left border
* widths. If an option wants to customize such a shortcut, it should be listed
* here with the non-shortcuts property it stands for, in order.
*
* @type {Object<string[]>}
*/
const CSS_SHORTHANDS = {
'border-width': ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],
'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
};
/**
* Key-value mapping to list converters from an unit A to an unit B.
* - The key is a string in the format '$1-$2' where $1 is the CSS symbol of
* unit A and $2 is the CSS symbol of unit B.
* - The value is a function that converts the received value (expressed in
* unit A) to another value expressed in unit B. Two other parameters is
* received: the css property on which the unit applies and the jQuery element
* on which that css property may change.
*/
const CSS_UNITS_CONVERSION = {
's-ms': () => 1000,
'ms-s': () => 0.001,
'rem-px': () => _computePxByRem(),
'px-rem': () => _computePxByRem(true),
};
/**
* Computes the number of "px" needed to make a "rem" unit. Subsequent calls
* returns the cached computed value.
*
* @param {boolean} [toRem=false]
* @returns {float} - number of px by rem if 'toRem' is false
* - the inverse otherwise
*/
function _computePxByRem(toRem) {
if (_computePxByRem.PX_BY_REM === undefined) {
const htmlStyle = window.getComputedStyle(document.documentElement);
_computePxByRem.PX_BY_REM = parseFloat(htmlStyle['font-size']);
}
return toRem ? (1 / _computePxByRem.PX_BY_REM) : _computePxByRem.PX_BY_REM;
}
/**
* Converts the given (value + unit) string to a numeric value expressed in
* the other given css unit.
*
* e.g. fct('400ms', 's') -> 0.4
*
* @param {string} value
* @param {string} unitTo
* @param {string} [cssProp] - the css property on which the unit applies
* @param {jQuery} [$target] - the jQuery element on which that css property
* may change
* @returns {number}
*/
function _convertValueToUnit(value, unitTo, cssProp, $target) {
const m = _getNumericAndUnit(value);
if (!m) {
return NaN;
}
const numValue = parseFloat(m[0]);
const valueUnit = m[1];
return _convertNumericToUnit(numValue, valueUnit, unitTo, cssProp, $target);
}
/**
* Converts the given numeric value expressed in the given css unit into
* the corresponding numeric value expressed in the other given css unit.
*
* e.g. fct(400, 'ms', 's') -> 0.4
*
* @param {number} value
* @param {string} unitFrom
* @param {string} unitTo
* @param {string} [cssProp] - the css property on which the unit applies
* @param {jQuery} [$target] - the jQuery element on which that css property
* may change
* @returns {number}
*/
function _convertNumericToUnit(value, unitFrom, unitTo, cssProp, $target) {
if (Math.abs(value) < Number.EPSILON || unitFrom === unitTo) {
return value;
}
const converter = CSS_UNITS_CONVERSION[`${unitFrom}-${unitTo}`];
if (converter === undefined) {
throw new Error(`Cannot convert '${unitFrom}' units into '${unitTo}' units !`);
}
return value * converter(cssProp, $target);
}
/**
* Returns the numeric value and unit of a css value.
*
* e.g. fct('400ms') -> [400, 'ms']
*
* @param {string} value
* @returns {Array|null}
*/
function _getNumericAndUnit(value) {
const m = value.trim().match(/^([0-9.]+)(\w*)$/);
if (!m) {
return null;
}
return [m[1], m[2]];
}
/**
* Checks if two css values are equal.
*
* @param {string} value1
* @param {string} value2
* @param {string} [cssProp] - the css property on which the unit applies
* @param {jQuery} [$target] - the jQuery element on which that css property
* may change
* @returns {boolean}
*/
function _areCssValuesEqual(value1, value2, cssProp, $target) {
// String comparison first
if (value1 === value2) {
return true;
}
// Convert the second value in the unit of the first one and compare
// floating values
const data = _getNumericAndUnit(value1);
if (!data) {
return false;
}
const numValue1 = data[0];
const numValue2 = _convertValueToUnit(value2, data[1], cssProp, $target);
return (Math.abs(numValue1 - numValue2) < Number.EPSILON);
}
return {
CSS_SHORTHANDS: CSS_SHORTHANDS,
CSS_UNITS_CONVERSION: CSS_UNITS_CONVERSION,
computePxByRem: _computePxByRem,
convertValueToUnit: _convertValueToUnit,
convertNumericToUnit: _convertNumericToUnit,
getNumericAndUnit: _getNumericAndUnit,
areCssValuesEqual: _areCssValuesEqual,
};
});
@@ -144,6 +144,15 @@ var SnippetEditor = Widget.extend({
// Public
//--------------------------------------------------------------------------
/**
* Checks whether the snippet options are shown or not.
*
* @returns {boolean}
*/
areOptionsShown: function () {
const lastIndex = this._customize$Elements.length - 1;
return !!this._customize$Elements[lastIndex].parent().length;
},
/**
* Notifies all the associated snippet options that the snippet has just
* been dropped in the page.
@@ -165,6 +174,17 @@ var SnippetEditor = Widget.extend({
option.cleanForSave();
});
},
/**
* Closes all widgets of all options.
*/
closeWidgets: function () {
if (!this.areOptionsShown()) {
return;
}
Object.keys(this.styles).forEach(key => {
this.styles[key].closeWidgets();
});
},
/**
* Makes the editor overlay cover the associated snippet.
*/
@@ -288,9 +308,7 @@ var SnippetEditor = Widget.extend({
return;
}
var lastIndex = this._customize$Elements.length - 1;
var optionsAlreadyShown = !!this._customize$Elements[lastIndex].parent().length;
if (optionsAlreadyShown === show) {
if (this.areOptionsShown() === show) {
return;
}
this.trigger_up('update_customize_elements', {
@@ -666,7 +684,6 @@ var SnippetsMenu = Widget.extend({
id: 'oe_snippets',
cacheSnippetTemplate: {},
events: {
'click we-select': '_onOptionTogglerClick',
'click .o_install_btn': '_onInstallBtnClick',
},
custom_events: {
@@ -684,6 +701,7 @@ var SnippetsMenu = Widget.extend({
'hide_overlay': '_onHideOverlay',
'block_preview_overlays': '_onBlockPreviewOverlays',
'unblock_preview_overlays': '_onUnblockPreviewOverlays',
'user_value_widget_opening': '_onUserValueWidgetOpening',
},
/**
@@ -1668,21 +1686,6 @@ var SnippetsMenu = Widget.extend({
_onCleanForSaveDemand: function (ev) {
this.cleanForSave();
},
/**
* @private
* @param {*} ev
*/
_onOptionTogglerClick: function (ev) {
var togglerEl = ev.currentTarget;
if (togglerEl.tagName !== 'WE-TOGGLER') {
togglerEl = togglerEl.querySelector('we-toggler');
}
var $hierarchyTogglers = $(togglerEl).parents('we-select').children('we-toggler');
this.$('we-select').children('we-toggler').not($hierarchyTogglers).removeClass('active');
$hierarchyTogglers.not(togglerEl).addClass('active');
togglerEl.classList.toggle('active');
},
/**
* Called when the overlay dimensions/positions should be recomputed.
*
@@ -1836,6 +1839,13 @@ var SnippetsMenu = Widget.extend({
this.customizePanel.classList.toggle('d-none', !customize);
this.$('.o_we_customize_snippet_btn').toggleClass('active', customize);
},
/**
* Called when an user value widget is being opened -> close all the other
* user value widgets of all editors.
*/
_onUserValueWidgetOpening: function () {
this.snippetEditors.forEach(editor => editor.closeWidgets());
},
});
return {
File diff suppressed because it is too large Load Diff
@@ -398,6 +398,14 @@ body.editor_enable.editor_has_snippets {
}
}
we-row we-input {
flex-wrap: wrap;
> we-title {
min-width: 95%;
}
}
.dropdown-menu {
// FIXME temporary fix for m2o option for example
position: static !important;
+1
View File
@@ -131,6 +131,7 @@
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/web_editor/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/common/ace.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/common/utils.js"/>
<script type="text/javascript" src="/web_editor/static/src/js/wysiwyg/root.js"/>
</xpath>
</template>
@@ -881,7 +881,7 @@ registry.gallery = publicWidget.Widget.extend({
srcs: urls,
index: idx,
dim: dimensions,
interval: milliseconds,
interval: milliseconds || 0,
id: _.uniqueId('slideshow_'),
}));
$modal.modal({
@@ -19,14 +19,7 @@ options.Class.include({
/**
* @override
*/
setDataAttribute: function (previewMode, dataName, $opt) {
this._super(...arguments);
this._refreshPublicWidgets();
},
/**
* @override
*/
selectDataAttribute: function (previewMode, value, $opt) {
selectDataAttribute: function (previewMode, widgetValue, params) {
this._super(...arguments);
this._refreshPublicWidgets();
},
@@ -86,19 +79,20 @@ options.registry.background.include({
/**
* @override
*/
_setActive: function () {
this._super(...arguments);
if (this._hasBgvideo()) {
this.$el.find('[data-choose-image]').addClass('active');
_computeWidgetState: function (methodName, params) {
if (methodName === 'chooseImage') {
return this._hasBgvideo() ? 'true' : '';
}
return this._super(...arguments);
},
/**
* Updates the background video used by the snippet.
*
* @private
* @see this.selectClass for parameters
* @returns {Promise}
*/
_setBgVideo: function (previewMode, value) {
_setBgVideo: async function (previewMode, value) {
this.$('> .o_bg_video_container').toggleClass('d-none', previewMode === true);
if (previewMode !== false) {
@@ -113,7 +107,7 @@ options.registry.background.include({
delete target.dataset.bgVideoSrc;
}
this._refreshPublicWidgets();
this._updateUI();
await this._updateUI();
},
/**
* Returns whether the current target has a background video or not.
@@ -142,15 +136,16 @@ options.registry.background.include({
/**
* @override
*/
_onSaveMediaDialog: function (data) {
_onSaveMediaDialog: async function (data) {
if (!data.bgVideoSrc) {
this._setBgVideo(false);
this._super(...arguments);
return;
const _super = this._super.bind(this);
const args = arguments;
await this._setBgVideo(false);
return _super(...args);
}
// if the user chose a video, only add the video without removing the
// background
this._setBgVideo(false, data.bgVideoSrc);
await this._setBgVideo(false, data.bgVideoSrc);
},
});
@@ -416,8 +411,8 @@ options.registry.CarouselItem = options.Class.extend({
*
* @see this.selectClass for parameters
*/
slide: function (previewMode, value) {
switch (value) {
slide: function (previewMode, widgetValue, params) {
switch (widgetValue) {
case 'left':
this.$controls.filter('.carousel-control-prev')[0].click();
break;
@@ -458,7 +453,7 @@ options.registry.navTabs = options.Class.extend({
*
* @see this.selectClass for parameters
*/
addTab: function (previewMode, value, $opt) {
addTab: function (previewMode, widgetValue, params) {
var $activeItem = this.$navLinks.filter('.active').parent();
var $activePane = this.$tabPanes.filter('.active');
@@ -477,7 +472,7 @@ options.registry.navTabs = options.Class.extend({
*
* @see this.selectClass for parameters
*/
removeTab: function (previewMode, value, $opt) {
removeTab: function (previewMode, widgetValue, params) {
var self = this;
var $activeLink = this.$navLinks.filter('.active');
@@ -531,8 +526,8 @@ options.registry.navTabs = options.Class.extend({
* @private
* @override
*/
_updateUI: function () {
this._super.apply(this, arguments);
_updateUI: async function () {
await this._super.apply(this, arguments);
this.$el.filter('[data-remove-tab]').toggleClass('d-none', this.$tabPanes.length <= 2);
},
});
@@ -607,14 +602,24 @@ options.registry.layout_column = options.Class.extend({
*
* @see this.selectClass for parameters
*/
selectCount: function (previewMode, value, $opt) {
this._updateColumnCount(value - this.$target.children().length);
selectCount: function (previewMode, widgetValue, params) {
const nbColumns = parseInt(widgetValue);
this._updateColumnCount(nbColumns - this.$target.children().length);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetState: function (methodName, params) {
if (methodName === 'selectCount') {
return '' + this.$target.children().length;
}
return this._super(...arguments);
},
/**
* Adds new columns which are clones of the last column or removes the
* last x columns.
@@ -664,14 +669,6 @@ options.registry.layout_column = options.Class.extend({
$columns.first().addClass('offset-lg-' + colOffset);
}
},
/**
* @override
*/
_setActive: function () {
this._super.apply(this, arguments);
this.$el.find('[data-select-count]').removeClass('active')
.filter('[data-select-count=' + this.$target.children().length + ']').addClass('active');
},
});
options.registry.parallax = options.Class.extend({
@@ -931,7 +928,7 @@ options.registry.ul = options.Class.extend({
/**
* @override
*/
toggleClass: function () {
selectClass: function () {
this._super.apply(this, arguments);
this.trigger_up('widgets_stop_request', {
@@ -1121,8 +1118,8 @@ options.registry.gallery = options.Class.extend({
'data-index': ++index,
}).appendTo($container);
}
self._reset();
self.trigger_up('cover_update');
this.mode('reset', this.getMode());
this.trigger_up('cover_update');
this._updateUI();
});
dialog.open();
@@ -1133,11 +1130,11 @@ options.registry.gallery = options.Class.extend({
*
* @see this.selectClass for parameters
*/
columns: function (previewMode, value) {
this.$target.attr('data-columns', value);
columns: function (previewMode, widgetValue, params) {
const nbColumns = parseInt(widgetValue || '1');
this.$target.attr('data-columns', nbColumns);
var $activeMode = this.$el.find('.active[data-mode]');
this.mode(null, $activeMode.data('mode'), $activeMode);
this.mode(previewMode, this.getMode(), {}); // TODO improve
},
/**
* Get the image target's layout mode (slideshow, masonry, grid or nomode).
@@ -1182,8 +1179,8 @@ options.registry.gallery = options.Class.extend({
* Allows to changes the interval of automatic slideshow (not active in
* edit mode).
*/
interval: function (previewMode, value) {
this.$target.find('.carousel:first').attr('data-interval', value);
interval: function (previewMode, widgetValue) {
this.$target.find('.carousel:first').attr('data-interval', widgetValue || '0');
},
/**
* Displays the images with the "masonry" layout.
@@ -1226,12 +1223,13 @@ options.registry.gallery = options.Class.extend({
*
* @see this.selectClass for parameters
*/
mode: function (previewMode, value, $opt) {
mode: function (previewMode, widgetValue, params) {
widgetValue = widgetValue || 'slideshow'; // FIXME should not be needed
this.$target.css('height', '');
this[value]();
this[widgetValue]();
this.$target
.removeClass('o_nomode o_masonry o_grid o_slideshow')
.addClass('o_' + value);
.addClass('o_' + widgetValue);
this.trigger_up('cover_update');
},
/**
@@ -1322,11 +1320,11 @@ options.registry.gallery = options.Class.extend({
*
* @override
*/
notify: function (name, data) {
this._super.apply(this, arguments);
notify: async function (name, data) {
await this._super(...arguments);
if (name === 'image_removed') {
data.$image.remove(); // Force the removal of the image before reset
this._reset();
this.mode('reset', this.getMode());
} else if (name === 'image_index_request') {
var imgs = this._getImages();
var position = _.indexOf(imgs, data.$image[0]);
@@ -1351,7 +1349,7 @@ options.registry.gallery = options.Class.extend({
// indexes were not the same as positions.
$(img).attr('data-index', index);
});
this._reset();
this.mode('reset', this.getMode());
}
},
@@ -1359,6 +1357,44 @@ options.registry.gallery = options.Class.extend({
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetState: function (methodName, params) {
switch (methodName) {
case 'mode': {
let activeModeName = 'slideshow';
for (const modeName of params.possibleValues) {
if (this.$target.hasClass(`o_${modeName}`)) {
activeModeName = modeName;
break;
}
}
this.activeMode = activeModeName;
return activeModeName;
}
case 'interval': {
const carousel = this.$target[0].querySelector('.carousel');
return (carousel && carousel.dataset.interval || '0');
}
case 'columns': {
return `${this._getColumns()}`;
}
case 'styling': {
const img = this.$target[0].querySelector('img');
if (!img) {
return '';
}
for (const className of params.possibleValues) {
if (className && img.classList.contains(className)) {
return className;
}
}
return '';
}
}
return this._super(...arguments);
},
/**
* Returns the images, sorted by index.
*
@@ -1407,57 +1443,17 @@ options.registry.gallery = options.Class.extend({
/**
* @override
*/
_setActive: function () {
this._super(...arguments);
_updateUI: async function () {
await this._super(...arguments);
var activeModeSelectors = [];
for (const className of this.$target[0].classList) {
if (className.startsWith('o_')) {
activeModeSelectors.push('[data-mode="' + className.substring(2) + '"]');
}
}
var activeMode = this.$el.find('[data-mode]')
.removeClass('active')
.filter(activeModeSelectors.join(', '))
.addClass('active')
.data('mode');
this.$el.find('[data-interval]').closest('we-select')[0]
.classList.toggle('d-none', this.activeMode !== 'slideshow');
var carousel = this.$target[0].querySelector('.carousel');
var activeInterval = carousel ? (carousel.dataset.interval || 0) : undefined;
var $intervalOptions = this.$el.find('[data-interval]');
$intervalOptions.removeClass('active')
.filter('[data-interval="' + activeInterval + '"]')
.addClass('active');
$intervalOptions.closest('we-select')[0]
.classList.toggle('d-none', activeMode !== 'slideshow');
var columns = this._getColumns();
var $columnOptions = this.$el.find('[data-columns]');
$columnOptions.removeClass('active')
.filter('[data-columns="' + columns + '"]')
.addClass('active');
$columnOptions.closest('we-select')[0]
.classList.toggle('d-none', !(activeMode === 'grid' || activeMode === 'masonry'));
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', activeMode === 'slideshow');
var $stylingOptions = this.$el.find('[data-styling]');
$stylingOptions.removeClass('active');
var img = this.$target[0].querySelector('img');
var activeStyleSelectors = [];
if (img) {
for (const className of img.classList) {
activeStyleSelectors.push('[data-styling="' + className + '"]');
}
}
var $toEnable = activeStyleSelectors.length
? $stylingOptions.filter(activeStyleSelectors.join(', '))
: null;
if (!$toEnable || !$toEnable.length) {
$toEnable = $stylingOptions.first();
}
$toEnable.addClass('active');
.classList.toggle('d-none', this.activeMode === 'slideshow');
},
});
@@ -1486,14 +1482,14 @@ options.registry.gallery_img = options.Class.extend({
*
* @see this.selectClass for parameters
*/
position: function (previewMode, value) {
position: function (previewMode, widgetValue, params) {
this.trigger_up('deactivate_snippet');
this.trigger_up('option_update', {
optionName: 'gallery',
name: 'image_index_request',
data: {
$image: this.$target,
position: value,
position: widgetValue,
},
});
},
@@ -1510,7 +1506,7 @@ options.registry.topMenuTransparency = options.Class.extend({
*
* @see this.selectClass for params
*/
transparent: function (previewMode, value, $opt) {
transparent: function (previewMode, widgetValue, params) {
var self = this;
this.trigger_up('action_demand', {
actionName: 'toggle_page_option',
@@ -1531,16 +1527,17 @@ options.registry.topMenuTransparency = options.Class.extend({
/**
* @override
*/
_setActive: function () {
this._super.apply(this, arguments);
this.trigger_up('action_demand', {
actionName: 'get_page_option',
params: ['header_overlay'],
onSuccess: value => {
this.$el.find('[data-transparent]').toggleClass('active', !!value);
},
});
_computeWidgetState: function (methodName, params) {
if (methodName === 'transparent') {
return new Promise(resolve => {
this.trigger_up('action_demand', {
actionName: 'get_page_option',
params: ['header_overlay'],
onSuccess: v => resolve(v ? 'true' : ''),
});
});
}
return this._super(...arguments);
},
});
@@ -1630,7 +1627,7 @@ options.registry.anchor = options.Class.extend({
/**
* @see this.selectClass for parameters
*/
openAnchorDialog: function (previewMode, value, $opt) {
openAnchorDialog: function (previewMode, widgetValue, params) {
var self = this;
var buttons = [{
text: _t("Save & copy"),
@@ -1757,14 +1754,14 @@ options.registry.CoverProperties = options.Class.extend({
/**
* @see this.selectClass for parameters
*/
clear: function (previewMode, value, $opt) {
this.selectClass(previewMode, '', $());
clear: function (previewMode, widgetValue, params) {
this.$target.removeClass('o_record_has_cover');
this.$image.css('background-image', '');
},
/**
* @see this.selectClass for parameters
*/
change: function (previewMode, value, $opt) {
change: function (previewMode, widgetValue, params) {
var $image = $('<img/>');
var background = this.$image.css('background-image');
if (background && background !== 'none') {
@@ -1780,8 +1777,7 @@ options.registry.CoverProperties = options.Class.extend({
var src = image.src;
this.$image.css('background-image', src ? ('url(' + src + ')') : '');
if (!this.$target.hasClass('o_record_has_cover')) {
var $opt = this.$el.find('.o_record_cover_opt_size_default[data-select-class]');
this.selectClass(previewMode, $opt.data('selectClass'), $opt);
this.$el.find('.o_record_cover_opt_size_default[data-select-class]').click();
}
this._updateUI();
});
@@ -1789,16 +1785,16 @@ options.registry.CoverProperties = options.Class.extend({
/**
* @see this.selectClass for parameters
*/
filterValue: function (previewMode, value, $opt) {
this.$filter.css('opacity', value);
filterValue: function (previewMode, widgetValue, params) {
this.$filter.css('opacity', widgetValue || 0);
},
/**
* @see this.selectClass for parameters
*/
filterColor: function (previewMode, value, $opt) {
filterColor: function (previewMode, widgetValue, params) {
this.$filter.removeClass(this.filterColorClasses);
if (value) {
this.$filter.addClass(value);
if (widgetValue) {
this.$filter.addClass(widgetValue);
}
var $firstVisibleFilterOpt = this.$filterValueOpts.eq(1);
@@ -1812,12 +1808,32 @@ options.registry.CoverProperties = options.Class.extend({
//--------------------------------------------------------------------------
/**
* @private
* @override
*/
_setActive: function () {
this._super.apply(this, arguments);
_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 () {
await this._super(...arguments);
// Only show options which are useful to the current cover
_.each(this.$el.children(), el => {
var $el = $(el);
@@ -1834,27 +1850,14 @@ options.registry.CoverProperties = options.Class.extend({
});
}
});
this.$el.find('[data-clear]').toggleClass('d-none', !this.$target.hasClass('o_record_has_cover'));
this.$filterValueOpts.removeClass('active');
this.$filterColorOpts.removeClass('active');
var activeFilterValue = this.$filterValueOpts
.filter((i, el) => {
return (parseFloat($(el).data('filterValue')).toFixed(1) === parseFloat(this.$filter.css('opacity')).toFixed(1));
}).addClass('active').data('filterValue');
var activeFilterColor = this.$filterColorOpts
.filter((i, el) => {
return this.$filter.hasClass($(el).data('filterColor'));
}).addClass('active').data('filterColor');
// Update saving dataset
this.$target[0].dataset.coverClass = this.$el.find('.active[data-cover-opt="size"]').data('selectClass') || '';
this.$target[0].dataset.textSizeClass = this.$el.find('.active[data-cover-opt="text_size"]').data('selectClass') || '';
this.$target[0].dataset.textAlignClass = this.$el.find('.active[data-cover-opt="text_align"]').data('selectClass') || '';
this.$target[0].dataset.filterValue = activeFilterValue || 0.0;
this.$target[0].dataset.filterColor = activeFilterColor || '';
this.$target[0].dataset.filterValue = this.$filterValueOpts.filter('.active').data('filterValue') || 0.0;
this.$target[0].dataset.filterColor = this.$filterColorOpts.filter('.active').data('filterColor') || '';
},
});
@@ -1878,35 +1881,6 @@ options.registry.SectionStretch = options.Class.extend({
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* @see this.selectClass for parameters
*/
toggleContainerFluid: function (previewMode, value, $opt) {
var isFluid = this.$el.find('[data-toggle-container-fluid]').hasClass('active');
this.$target.toggleClass('container', !isFluid)
.toggleClass('container-fluid', isFluid);
if (previewMode !== 'reset') {
this.$target.toggleClass('container').toggleClass('container-fluid');
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_setActive: function () {
this._super.apply(this, arguments);
var isFluid = this.$target.hasClass('container-fluid');
this.$el.find('[data-toggle-container-fluid]').toggleClass('active', isFluid);
},
});
/**
@@ -1922,10 +1896,6 @@ options.registry.SnippetMove = options.Class.extend({
$overlayArea.prepend($buttons[0]);
$overlayArea.append($buttons[1]);
// TODO this is kinda hack but not worth a complex system while no
// other use case is implemented.
$buttons.on('click', this._onOptionSelection.bind(this));
return this._super(...arguments);
},
@@ -1938,8 +1908,8 @@ options.registry.SnippetMove = options.Class.extend({
*
* @see this.selectClass for parameters
*/
moveSnippet: function (previewMode, value, $opt) {
switch (value) {
moveSnippet: function (previewMode, widgetValue, params) {
switch (widgetValue) {
case 'prev':
this.$target.prev().before(this.$target);
break;
+10 -8
View File
@@ -1108,7 +1108,7 @@
<we-divider/>
<we-button data-select-class="s_carousel_default">Default</we-button>
</we-select>
<we-input string="Speed" data-set-data-attribute="interval" data-unit="s" data-save-unit="ms" data-step="0.1"/>
<we-input string="Speed" data-select-data-attribute="0s" data-attribute-name="interval" data-unit="s" data-save-unit="ms" data-step="0.1"/>
</div>
<div data-js="CarouselItem"
@@ -1238,8 +1238,10 @@
<we-button class="fas fa-fw fa-expand-arrows-alt" title="Background Position"
data-background-position-overlay="true" data-no-preview="true"/>
</we-row>
<we-input string="Background width" data-set-style="background-size" data-default-value="auto"/>
<we-input string="Background height" data-set-style="background-size" data-default-value="auto"/>
<we-multi data-css-property="background-size">
<we-input string="Width" data-select-style="auto" data-unit="px"/>
<we-input string="Height" data-select-style="auto" data-unit="px"/>
</we-multi>
</div>
<!-- Parallax -->
@@ -1275,9 +1277,9 @@
<div data-selector="section .row > div"
data-exclude=".s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .o_gallery .row > div">
<we-input string="Border" data-set-style="border-width" data-extra-class="border"/>
<we-input string="Rounded" data-set-style="border-radius" data-extra-class="rounded"/>
<we-checkbox string="Shadow" data-toggle-class="shadow"/>
<we-input string="Border" data-select-style="0" data-css-property="border-width" data-unit="px" data-extra-class="border"/>
<we-input string="Rounded" data-select-style="0" data-css-property="border-radius" data-unit="px" data-extra-class="rounded"/>
<we-checkbox string="Shadow" data-select-class="shadow"/>
</div>
<div data-js="sizing_y"
@@ -1301,7 +1303,7 @@
<div data-js="ul"
data-selector=":not(li) > ul:has(ul,ol), :not(li) > ol:has(ul,ol)">
<we-checkbox string="Folded list" data-toggle-class="o_ul_folded"/>
<we-checkbox string="Folded list" data-select-class="o_ul_folded"/>
</div>
<div data-js="menu_data"
@@ -1391,7 +1393,7 @@
<div data-js="SectionStretch"
data-selector="section"
data-target="> .container, > .container-fluid">
<we-checkbox string="Stretch" data-toggle-container-fluid=""/>
<we-checkbox string="Stretch" data-select-class="container|container-fluid" data-no-preview="true"/>
</div>
</template>
</odoo>
@@ -5,44 +5,6 @@ var sOptions = require('web_editor.snippets.options');
var wUtils = require('website.utils');
sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
/**
* @override
*/
start: function () {
var def = this._rpc({
model: 'blog.blog',
method: 'search_read',
args: [wUtils.websiteDomain(this), ['name']],
}).then(blogs => {
var allBlogsEl = this.el.querySelector('[data-filter-by-blog-id="0"]');
var menuEl = allBlogsEl.parentNode;
for (const blog of blogs) {
let el = allBlogsEl.cloneNode();
el.dataset.filterByBlogId = blog.id;
el.textContent = blog.name;
menuEl.appendChild(el);
}
this._updateUI();
});
return Promise.all([this._super.apply(this, arguments), def]);
},
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* @see this.selectClass for parameters
*/
filterByBlogId: function (previewMode, value, $opt) {
value = parseInt(value);
this.$target.attr('data-filter-by-blog-id', value).data('filterByBlogId', value);
this.trigger_up('widgets_start_request', {
editableMode: true,
$target: this.$target,
});
},
//--------------------------------------------------------------------------
// Private
@@ -51,13 +13,21 @@ sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
/**
* @override
*/
_setActive: function () {
this._super.apply(this, arguments);
var activeBlogId = this.$target.data('filterByBlogId') || 0;
this.$el.find('[data-filter-by-blog-id]').removeClass('active');
this.$el.find('[data-filter-by-blog-id=' + activeBlogId + ']').addClass('active');
_renderCustomXML: function (uiFragment) {
return this._rpc({
model: 'blog.blog',
method: 'search_read',
args: [wUtils.websiteDomain(this), ['name']],
}).then(blogs => {
const menuEl = uiFragment.querySelector('[name="blog_selection"]');
for (const blog of blogs) {
const el = document.createElement('we-button');
el.dataset.selectDataAttribute = blog.id;
el.textContent = blog.name;
menuEl.appendChild(el);
}
return this._updateUI();
});
},
});
});
@@ -111,8 +111,8 @@ options.registry.CoverProperties.include({
/**
* @override
*/
_updateUI: function () {
this._super(...arguments);
_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"]');
var $coverMid = this.$el.find('[data-select-class*="cover_mid"]');
+2 -2
View File
@@ -11,8 +11,8 @@
<template id="s_latest_posts_options" inherit_id="website.snippet_options">
<xpath expr="//div[@data-js='background']" position="before">
<div data-js="js_get_posts_selectBlog" data-selector=".s_latest_posts, .s_latest_posts_big_picture" data-target=".js_get_posts" data-no-check="true">
<we-select string="Choose a blog" data-no-preview="true">
<we-button data-filter-by-blog-id="0">All blogs</we-button>
<we-select string="Choose a blog" data-no-preview="true" name="blog_selection" data-attribute-name="filterByBlogId">
<we-button data-select-data-attribute="0">All blogs</we-button>
<!-- the blog list will be generated in js -->
</we-select>
</div>
@@ -91,7 +91,7 @@ odoo.define('website_form_editor', function (require) {
},
// Choose a model modal
website_form_model_modal: function (previewMode, value, $li) {
website_form_model_modal: function (previewMode, widgetValue, params) {
var self = this;
this._rpc({
model: "ir.model",
@@ -224,7 +224,7 @@ odoo.define('website_form_editor', function (require) {
},
// Choose a field modal
website_form_field_modal: function (previewMode, value, $li) {
website_form_field_modal: function (previewMode, widgetValue, params) {
var self = this;
this.fields().then(function (fields) {
@@ -261,13 +261,13 @@ odoo.define('website_form_editor', function (require) {
},
// Create a custom field
website_form_custom_field: function (previewMode, value, $li) {
var default_field_name = 'Custom ' + $li.text();
website_form_custom_field: function (previewMode, widgetValue, params) {
var default_field_name = 'Custom ' + this.el.querySelector(`[data-website_form_custom_field="${widgetValue}"]`).textContent; // TODO improve
this.append_field({
name: default_field_name,
string: default_field_name,
custom: true,
type: value,
type: widgetValue,
// Default values for x2many fields
records: [
{
@@ -302,7 +302,7 @@ odoo.define('website_form_editor', function (require) {
},
// Re-render the field and replace the current one
// website_form_editor_field_reset: function(previewMode, value, $li) {
// website_form_editor_field_reset: function(previewMode, widgetValue, params) {
// var self = this;
// var target_field_name = this.$target.find('.col-form-label').attr('for');
// this.fields().then(function(fields){
@@ -476,9 +476,9 @@ odoo.define('website_form_editor', function (require) {
/**
* @see this.selectClass for parameters
*/
website_form_choice_field_display: function (previewMode, value, $opt) {
this.$target.toggleClass('o_website_form_flex_fw', value === 'vertical');
this.$target[0].dataset.display = value;
website_form_choice_field_display: function (previewMode, widgetValue, params) {
this.$target.toggleClass('o_website_form_flex_fw', widgetValue === 'vertical');
this.$target[0].dataset.display = widgetValue;
},
//----------------------------------------------------------------------
@@ -488,12 +488,11 @@ odoo.define('website_form_editor', function (require) {
/**
* @override
*/
_setActive: function () {
this._super(...arguments);
this.$el.find('[data-website_form_choice_field_display]')
.removeClass('active')
.filter('[data-website_form_choice_field_display=' + this.$target.attr('data-display') + ']')
.addClass('active');
_computeWidgetState: function (methodName, params) {
if (methodName === 'website_form_choice_field_display') {
return this.$target.attr('data-display');
}
return this._super(...arguments);
},
});
@@ -502,7 +501,7 @@ odoo.define('website_form_editor', function (require) {
xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
// Option to toggle inputs required attribute
website_form_field_require: function (previewMode, value, $li) {
website_form_field_require: function (previewMode, widgetValue, params) {
this.$target.find('.o_website_form_input').each(function (index, input) {
input.required = !input.required;
});
@@ -115,7 +115,7 @@ odoo.define('website_form_editor.tour', function(require) {
},
{
content: "Click on Hidden",
trigger: "[data-toggle-class='o_website_form_field_hidden']"
trigger: "[data-select-class='o_website_form_field_hidden']"
},
{
content: "Check the resulting field",
+2 -2
View File
@@ -51,7 +51,7 @@
<!-- Field -->
<div data-js='website_form_editor' data-selector=".form-field" data-drop-near=".form-field">
<we-checkbox string="Hidden" data-no-preview="true"
data-toggle-class="o_website_form_field_hidden"/>
data-select-class="o_website_form_field_hidden"/>
</div>
<!-- Add an option to display radio buttons / checkboxes horizontally or vertically -->
@@ -72,7 +72,7 @@
<div data-js='website_form_editor_field' data-selector=".form-field:not(.o_website_form_required)">
<we-checkbox string="Required" data-no-preview="true"
data-website_form_field_require=""
data-toggle-class="o_website_form_required_custom"/>
data-select-class="o_website_form_required_custom"/>
</div>
<!-- Remove the duplicate options of model fields -->
@@ -113,8 +113,8 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
/**
* @see this.selectClass for params
*/
setPpg: function (previewMode, value, $opt) {
const ppg = parseInt($opt.find('input').val());
setPpg: function (previewMode, widgetValue, params) {
const ppg = parseInt(widgetValue);
if (!ppg || ppg < 1) {
return false;
}
@@ -129,12 +129,12 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
/**
* @see this.selectClass for params
*/
setPpr: function (previewMode, value, $opt) {
this.ppr = parseInt(value);
setPpr: function (previewMode, widgetValue, params) {
this.ppr = parseInt(widgetValue);
this._rpc({
route: '/shop/change_ppr',
params: {
'ppr': value,
'ppr': this.ppr,
},
}).then(reload);
},
@@ -146,18 +146,16 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
/**
* @override
*/
_setActive: function () {
this._super(...arguments);
this.$el.find('[data-set-ppr]')
.removeClass('active')
.filter((i, el) => {
var nbColumns = parseInt($(el).data('setPpr'));
return nbColumns === this.ppr;
})
.addClass('active');
this.$el.find('[data-set-ppg] > input').val(this.ppg);
_computeWidgetState: function (methodName, params) {
switch (methodName) {
case 'setPpg': {
return this.ppg;
}
case 'setPpr': {
return this.ppr;
}
}
return this._super(...arguments);
},
});
@@ -172,29 +170,11 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
/**
* @override
*/
start: function () {
willStart: function () {
this.ppr = this.$target.closest('[data-ppr]').data('ppr');
this.productTemplateID = parseInt(this.$target.find('[data-oe-model="product.template"]').data('oe-id'));
var defs = [this._super.apply(this, arguments)];
defs.push(this._rpc({
model: 'product.style',
method: 'search_read',
}).then(data => {
var $menu = this.$el.find('[name="style"]');
for (var k in data) {
$menu.append(this._buildCheckboxElement(data[k]['name'], {
dataAttributes: {
'style': data[k]['id'],
'toggleClass': data[k]['html_class'],
},
}));
}
this._updateUI();
}));
return $.when.apply($, defs);
return this._super(...arguments);
},
/**
* @override
@@ -212,24 +192,24 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
/**
* @see this.selectClass for params
*/
style: function (previewMode, value, $opt) {
style: function (previewMode, widgetValue, params) {
this._rpc({
route: '/shop/change_styles',
params: {
'id': this.productTemplateID,
'style_id': value,
'style_id': params.possibleValues[params.possibleValues.length - 1],
},
});
},
/**
* @see this.selectClass for params
*/
changeSequence: function (previewMode, value, $opt) {
changeSequence: function (previewMode, widgetValue, params) {
this._rpc({
route: '/shop/change_sequence',
params: {
id: this.productTemplateID,
sequence: value,
sequence: widgetValue,
},
}).then(reload);
},
@@ -241,8 +221,34 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
/**
* @override
*/
_updateUI: function () {
this._super.apply(this, arguments);
_renderCustomWidgets: async function (uiFragment) {
const checkboxes = [];
return this._rpc({
model: 'product.style',
method: 'search_read',
}).then(async data => {
for (var k in data) {
const checkboxWidget = this._registerUserValueWidget('we-checkbox', this, data[k]['name'], {
dataAttributes: {
'style': data[k]['id'],
'selectClass': data[k]['html_class'],
},
});
checkboxes.push(checkboxWidget);
await checkboxWidget.appendTo(document.createDocumentFragment());
}
}).then(() => {
const menuEl = uiFragment.querySelector('[name="style"]');
for (const checkbox of checkboxes) {
menuEl.appendChild(checkbox.el);
}
});
},
/**
* @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);
@@ -277,8 +283,8 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
_onTableItemMouseEnter: function (ev) {
var $td = $(ev.currentTarget);
var $table = $td.closest("table");
var x = $td.index()+1;
var y = $td.parent().index()+1;
var x = $td.index() + 1;
var y = $td.parent().index() + 1;
var tr = [];
for (var yi = 0; yi < y; yi++) {
@@ -334,7 +340,7 @@ options.registry.ProductsSearchBar = options.Class.extend({
/**
* @see this.selectClass for parameters
*/
openSearchbarSettings: function (previewMode, value, $opt) {
openSearchbarSettings: function (previewMode, widgetValue, params) {
var self = this;
new Dialog(this, {
title: _t("Products Search Bar"),
+1 -1
View File
@@ -60,7 +60,7 @@
<div data-js="WebsiteSaleGridLayout"
data-selector="#products_grid .o_wsale_products_grid_table_wrapper > table"
data-no-check="true">
<we-input string="Number of products" data-set-ppg="" data-unit="" data-no-preview="true"/>
<we-input string="Number of products" data-set-ppg="" data-no-preview="true"/>
<we-select string="Number of Columns" class="o_wsale_ppr_submenu" data-no-preview="true">
<we-button data-set-ppr="2">2</we-button>
<we-button data-set-ppr="3">3</we-button>