summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2019-12-13 14:15:38 +0000
committerqsm-odoo <qsm@odoo.com>2019-12-16 09:14:01 +0000
commit2cc8316c669f41c71b2fb73ed416f1118e62f9c1 (patch)
treeac9a2a94e359422bbc9887f1cfbc171a3501c09a /addons/web_editor/static/src
parent684a19f0c1e1cc84df6b49e5a41ebe2fc53c2f39 (diff)
[IMP] web_editor: add new way to toggle left panel widget visibility
Now, instead of extending updateUI, developers can extend _computeVisibility and _computeWidgetVisibility to indicate if the whole option or a specific widget needs to be visible or not. Without any JS, developers can also add a 'data-dependencies' attribute on widgets whose value is a comma-separated list of widget names. Widget names are specificied through 'data-name'. Other notable changes: 1) *All* the options UI of a same section are updated on a change (not only the widgets of the same option). 2) Back as it was before: the UI is only updated in non-preview mode (prevent flickering of background removal during color preview because of point 1). Part of https://github.com/odoo/odoo/pull/41600 closes odoo/odoo#41600 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.editor.js40
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js237
2 files changed, 216 insertions, 61 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 7ce827f485f..6ca6263fbe2 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -25,6 +25,8 @@ var SnippetEditor = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'],
custom_events: {
'option_update': '_onOptionUpdate',
+ 'user_value_widget_request': '_onUserValueWidgetRequest',
+ 'snippet_option_update': '_onSnippetOptionUpdate',
},
/**
@@ -676,6 +678,44 @@ var SnippetEditor = Widget.extend({
this.trigger_up('request_history_undo_record', {$target: this.$target});
this.removeSnippet();
},
+ /**
+ * @private
+ * @param {OdooEvent}
+ */
+ _onSnippetOptionUpdate: async function (ev) {
+ if (ev.data.previewMode) {
+ ev.data.onSuccess();
+ return;
+ }
+
+ const proms1 = Object.keys(this.styles).map(key => {
+ return this.styles[key].updateUI({
+ forced: ev.data.widget,
+ noVisibility: true,
+ });
+ });
+ await Promise.all(proms1);
+
+ const proms2 = Object.keys(this.styles).map(key => {
+ return this.styles[key].updateUIVisibility();
+ });
+ await Promise.all(proms2);
+
+ ev.data.onSuccess();
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onUserValueWidgetRequest: function (ev) {
+ for (const key of Object.keys(this.styles)) {
+ const widget = this.styles[key].findWidget(ev.data.name);
+ if (widget) {
+ ev.data.onSuccess(widget);
+ return;
+ }
+ }
+ },
});
/**
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 812cd4bd7fa..4a465ffe3df 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -149,6 +149,22 @@ const UserValueWidget = Widget.extend({
this._userValueWidgets.forEach(widget => widget.close());
},
/**
+ * @param {string} name
+ * @returns {UserValueWidget|null}
+ */
+ findWidget: function (name) {
+ for (const widget of this._userValueWidgets) {
+ if (widget.getName() === name) {
+ return widget;
+ }
+ const depWidget = widget.findWidget(name);
+ if (depWidget) {
+ return depWidget;
+ }
+ }
+ return null;
+ },
+ /**
* Returns the value that the widget would hold if it was active, by default
* the internal value it holds.
*
@@ -170,6 +186,12 @@ const UserValueWidget = Widget.extend({
return possibleValues && possibleValues[0] || '';
},
/**
+ * @returns {string[]}
+ */
+ getDependencies: function () {
+ return this._dependencies;
+ },
+ /**
* Returns the names of the option methods associated to the widget. Those
* are loaded with @see loadMethodsData.
*
@@ -195,6 +217,12 @@ const UserValueWidget = Widget.extend({
return params;
},
/**
+ * @returns {string} empty string if no name is used by the widget
+ */
+ getName: function () {
+ return this._methodsParams.name || '';
+ },
+ /**
* Returns the user value that the widget currently holds. The value is a
* string, this is the value that will be received in the option methods
* of SnippetOptionWidget instances.
@@ -254,11 +282,14 @@ const UserValueWidget = Widget.extend({
this._methodsNames = [];
this._methodsParams = _.extend({}, extraParams);
this._methodsParams.optionsPossibleValues = {};
+ this._dependencies = [];
for (const key in this.el.dataset) {
const dataValue = this.el.dataset[key].trim();
- if (validMethodNames.includes(key)) {
+ if (key === 'dependencies') {
+ this._dependencies.push(...dataValue.split(/\s*,\s*/g));
+ } else if (validMethodNames.includes(key)) {
this._methodsNames.push(key);
this._methodsParams.optionsPossibleValues[key] = dataValue.split(/\s*\|\s*/g);
} else {
@@ -314,6 +345,12 @@ const UserValueWidget = Widget.extend({
this._value = value;
},
/**
+ * @param {boolean} show
+ */
+ toggleVisibility: function (show) {
+ this.el.classList.toggle('d-none', !show);
+ },
+ /**
* Updates the UI to match the user value the widget currently holds, only
* if the UI can currently be updated.
*
@@ -1428,6 +1465,22 @@ const SnippetOptionWidget = Widget.extend({
this._userValueWidgets.forEach(widget => widget.close());
},
/**
+ * @param {string} name
+ * @returns {UserValueWidget|null}
+ */
+ findWidget: function (name) {
+ for (const widget of this._userValueWidgets) {
+ if (widget.getName() === name) {
+ return widget;
+ }
+ const depWidget = widget.findWidget(name);
+ if (depWidget) {
+ return depWidget;
+ }
+ }
+ return null;
+ },
+ /**
* Sometimes, options may need to notify other options, even in parent
* editors. This can be done thanks to the 'option_update' event, which
* will then be handled by this function.
@@ -1453,16 +1506,18 @@ const SnippetOptionWidget = Widget.extend({
setTarget: async function ($target) {
this.$target = $target;
await this.updateUI();
- this.$target.trigger('snippet-option-change', [this]);
},
/**
* Updates the UI. For widget update, @see _computeWidgetState.
*
* @param {UserValueWidget} [forced=null]
* Only non-previewed widgets are updated, except for the one given here
+ * @param {boolean} [noVisibility=false]
+ * If true, only update widget values and their UI, not their visibility
+ * -> @see updateUIVisibility for toggling visibility only
* @returns {Promise}
*/
- updateUI: async function (forced) {
+ updateUI: async function ({forced, noVisibility} = {}) {
// 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.
@@ -1494,6 +1549,49 @@ const SnippetOptionWidget = Widget.extend({
// hold have been updated).
return widget.updateUI(widget === forced);
});
+
+ if (!noVisibility) {
+ await this.updateUIVisibility();
+ }
+
+ return Promise.all(proms);
+ },
+ /**
+ * Updates the UI visibility - @see _computeVisibility. For widget update,
+ * @see _computeWidgetVisibility.
+ *
+ * @returns {Promise}
+ */
+ updateUIVisibility: async function () {
+ const proms = this._userValueWidgets.map(async widget => {
+ const params = widget.getMethodsParams();
+ const show = await this._computeWidgetVisibility(widget.getName(), params);
+ if (!show) {
+ widget.toggleVisibility(false);
+ return;
+ }
+
+ const dependencies = widget.getDependencies();
+ const dependenciesOK = !dependencies.length || dependencies.some(depName => {
+ const toBeActive = (depName[0] !== '!');
+ if (!toBeActive) {
+ depName = depName.substr(1);
+ }
+
+ let widget = null;
+ this.trigger_up('user_value_widget_request', {
+ name: depName,
+ onSuccess: _widget => widget = _widget,
+ });
+ return widget && (widget.isActive() === toBeActive);
+ });
+
+ widget.toggleVisibility(dependenciesOK);
+ });
+
+ const showUI = await this._computeVisibility();
+ this.el.classList.toggle('d-none', !showUI);
+
return Promise.all(proms);
},
@@ -1502,6 +1600,13 @@ const SnippetOptionWidget = Widget.extend({
//--------------------------------------------------------------------------
/**
+ * @private
+ * @returns {Promise<boolean>|boolean}
+ */
+ _computeVisibility: async function () {
+ return true;
+ },
+ /**
* Returns the string value that should be hold by the widget which is
* related to the given method name.
*
@@ -1567,6 +1672,15 @@ const SnippetOptionWidget = Widget.extend({
},
/**
* @private
+ * @param {string} widgetName
+ * @param {Object} params
+ * @returns {Promise<boolean>|boolean}
+ */
+ _computeWidgetVisibility: async function (widgetName, params) {
+ return true;
+ },
+ /**
+ * @private
* @param {HTMLElement} el
* @returns {Object}
*/
@@ -1598,25 +1712,25 @@ const SnippetOptionWidget = Widget.extend({
return;
}
+ // If it is not preview mode, the user selected the option for good
+ // (so record the action)
+ if (!previewMode) {
+ this.trigger_up('request_history_undo_record', {$target: this.$target});
+ }
+
// Call widget option methods and update $target
await this._select(previewMode, widget);
-
- // Trigger the related events notifying snippet changes
- let evName = 'snippet-option-reset';
- if (previewMode === false) {
- evName = 'snippet-option-change';
- } else if (previewMode === true) {
- evName = 'snippet-option-preview';
- }
- this.$target.trigger(evName, [this]);
this.$target.trigger('content_changed');
- // Update the UI of the correct widgets
- if (!previewMode) {
- await this.updateUI(widget);
- } else {
- await this.updateUI();
- }
+ await new Promise(resolve => {
+ // Will update the UI of the correct widgets for all options
+ // related to the same $target/editor if necessary
+ this.trigger_up('snippet_option_update', {
+ widget: widget,
+ previewMode: previewMode,
+ onSuccess: () => resolve(),
+ });
+ });
}});
},
/**
@@ -1754,12 +1868,6 @@ const SnippetOptionWidget = Widget.extend({
* @returns {Promise}
*/
_select: async function (previewMode, widget) {
- // If it is not preview mode, the user selected the option for good
- // (so record the action)
- if (!previewMode) {
- this.trigger_up('request_history_undo_record', {$target: this.$target});
- }
-
// Call each option method sequentially
for (const methodName of widget.getMethodsNames()) {
const widgetValue = widget.getValue(methodName);
@@ -2099,7 +2207,7 @@ registry.background = SnippetOptionWidget.extend({
if (previewMode === 'reset') {
// No background has been selected and we want to reset back to the
// original custom image
- await this._setCustomBackground(this.__customImageSrc);
+ await this._setCustomBackground(this.__customImageSrc, previewMode);
return;
}
@@ -2120,16 +2228,21 @@ registry.background = SnippetOptionWidget.extend({
var options = this._getMediaDialogOptions();
var media = this._getEditableMedia();
- var _editor = new weWidgets.MediaDialog(this, options, media).open();
- _editor.on('save', this, data => {
- this._onSaveMediaDialog(data).then(() => {
- this.$target.trigger('content_changed');
+ return new Promise(resolve => {
+ const _editor = new weWidgets.MediaDialog(this, options, media).open();
+ let _saving = false;
+ _editor.on('save', this, data => {
+ _saving = true;
+ this._onSaveMediaDialog(data).then(() => resolve());
+ });
+ _editor.on('closed', this, () => {
+ if (media.classList.contains('o_we_fake_image')) {
+ media.parentNode.removeChild(media);
+ }
+ if (!_saving) {
+ resolve();
+ }
});
- });
- _editor.on('closed', this, () => {
- if (media.classList.contains('o_we_fake_image')) {
- media.parentNode.removeChild(media);
- }
});
},
@@ -2163,7 +2276,6 @@ registry.background = SnippetOptionWidget.extend({
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];
@@ -2171,6 +2283,14 @@ registry.background = SnippetOptionWidget.extend({
this.editBgTextEl.textContent = this._getDefaultTextContent();
}
},
+ /**
+ * @override
+ */
+ updateUIVisibility: async function () {
+ await this._super(...arguments);
+ var src = this._getSrcFromCssValue();
+ this.removeBgWidget.el.classList.toggle('d-none', !src);
+ },
//--------------------------------------------------------------------------
// Private
@@ -2267,12 +2387,18 @@ registry.background = SnippetOptionWidget.extend({
* @param {string} value
* @returns {Promise}
*/
- _setCustomBackground: async function (value) {
+ _setCustomBackground: async function (value, previewMode) {
this.__customImageSrc = value;
this.background(false, this.__customImageSrc, {});
this.$target.toggleClass('oe_custom_bg', !!value);
- await this.updateUI();
- this.$target.trigger('snippet-option-change', [this]);
+ await new Promise(resolve => {
+ // Will update the UI of the correct widgets for all options
+ // related to the same $target/editor
+ this.trigger_up('snippet_option_update', {
+ previewMode: previewMode,
+ onSuccess: () => resolve(),
+ });
+ });
},
//--------------------------------------------------------------------------
@@ -2326,15 +2452,6 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
this._super.apply(this, arguments);
this._initOverlay();
- this.img = document.createElement('img');
- this.img.src = this._getSrcFromCssValue();
-
- this.$target.on('snippet-option-change', () => {
- // Hides option if the bg image is removed in favor of a bg color
- this.updateUI();
- // this.img is used to compute dragging speed
- this.img.src = this._getSrcFromCssValue();
- });
// Resize overlay content on window resize because background images
// change size, and on carousel slide because they sometimes take up
@@ -2369,7 +2486,14 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
*
* @see this.selectClass for params
*/
- backgroundPositionOverlay: function (previewMode, widgetValue, params) {
+ backgroundPositionOverlay: async function (previewMode, widgetValue, params) {
+ // Updates the internal image
+ await new Promise(resolve => {
+ this.img = document.createElement('img');
+ this.img.addEventListener('load', () => resolve());
+ this.img.src = this._getSrcFromCssValue();
+ });
+
const position = this.$target.css('background-position').split(' ').map(v => parseInt(v));
// Convert % values to pixels (because mouse movement is in pixels)
const delta = this._getBackgroundDelta();
@@ -2395,24 +2519,15 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
},
//--------------------------------------------------------------------------
- // Public
+ // Private
//--------------------------------------------------------------------------
+
/**
- * 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');
+ _computeVisibility: function () {
+ return this._super(...arguments) && (this.$target.css('background-image') !== 'none');
},
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
/**
* @override
*/