[IMP] web_editor: allow options to target subelements of a snippet

Add the ability for a snippet option's method to target multiple
subelements of the option's $target using the data-apply-to attribute
with a jQuery selector. This is achieved by calling the method on a
proxy of the object where accesses to the $target are redirected to a
different object.

While it may seem preferable to just set the $target before calling the
method and restoring it after the call, as this would be much simpler
code, it is not possible as this breaks down under asynchronous
conditions, hence the need to operate on a proxy such that all
concurrent asynchronous operations have their own independent $target
available for their entire time of execution.

Part of https://github.com/odoo/odoo/pull/41402
This commit is contained in:
Samuel Degueldre
2019-12-06 09:45:30 +00:00
committed by qsm-odoo
parent ae9c2487c8
commit 65262c5e3e
@@ -76,6 +76,31 @@ function _buildRowElement(title, options) {
return groupEl;
}
/**
* Creates a proxy for an object where one property is replaced by a different
* value. This value is captured in the closure and can be read and written to.
*
* @param {Object} obj - the object for which to create a proxy
* @param {string} propertyName - the name/key of the property to replace
* @param {*} value - the initial value to give to the property's copy
* @returns {Proxy} a proxy of the object with the property replaced
*/
function createPropertyProxy(obj, propertyName, value) {
return new Proxy(obj, {
get: function (obj, prop) {
if (prop === propertyName) {
return value;
}
return obj[prop];
},
set: function (obj, prop, val) {
if (prop === propertyName) {
return (value = val);
}
return Reflect.set(...arguments);
},
});
}
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@@ -1330,7 +1355,18 @@ const SnippetOptionWidget = Widget.extend({
const methodsNames = widget.getMethodsNames();
const proms = methodsNames.map(async methodName => {
const value = await this._computeWidgetState(methodName, widget.getMethodsParams(methodName));
const params = widget.getMethodsParams(methodName);
let obj = this;
if (params.applyTo) {
const $firstSubTarget = this.$(params.applyTo).eq(0);
if (!$firstSubTarget.length) {
return;
}
obj = createPropertyProxy(this, '$target', $firstSubTarget);
}
const value = await this._computeWidgetState.call(obj, methodName, params);
if (value === undefined) {
return;
}
@@ -1575,7 +1611,18 @@ const SnippetOptionWidget = Widget.extend({
}
widget.getMethodsNames().forEach(methodName => {
this[methodName](previewMode, widget.getValue(methodName), widget.getMethodsParams(methodName));
const widgetValue = widget.getValue(methodName);
const params = widget.getMethodsParams(methodName);
if (params.applyTo) {
const $subTargets = this.$(params.applyTo);
_.each($subTargets, subTargetEl => {
const proxy = createPropertyProxy(this, '$target', $(subTargetEl));
this[methodName].call(proxy, previewMode, widgetValue, params);
});
} else {
this[methodName](previewMode, widgetValue, params);
}
});
if (!previewMode) {