diff options
| author | Aaron Bohy <aab@odoo.com> | 2020-09-18 13:06:57 +0000 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2020-10-02 08:56:08 +0000 |
| commit | 8f4b2eefded0e8415b12804f26467f209878cd21 (patch) | |
| tree | 104235fc0744703bbbaf363557e6e0edcef6009a /addons/web/static/lib | |
| parent | a1624b0343061f980159d92be275bf85adf8ea03 (diff) | |
[FIX] web: update owl to last version
This commit updates owl from v1.0.9 to v1.0.10.
See [1] for the list of fixes it contains.
[1] https://github.com/odoo/owl/commit/e73fb462c5d70011ab667c1c529b85749a6bcf4a
closes odoo/odoo#58955
X-original-commit: 1682c6b73c39c693e0fcdb4da70891ef55edc3fd
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
Diffstat (limited to 'addons/web/static/lib')
| -rw-r--r-- | addons/web/static/lib/owl/owl.js | 201 |
1 files changed, 137 insertions, 64 deletions
diff --git a/addons/web/static/lib/owl/owl.js b/addons/web/static/lib/owl/owl.js index be810ab740a..fd003f9f25b 100644 --- a/addons/web/static/lib/owl/owl.js +++ b/addons/web/static/lib/owl/owl.js @@ -92,7 +92,10 @@ }
notifyCB() { }
observe(value, parent) {
- if (value === null || typeof value !== "object" || value instanceof Date) {
+ if (value === null ||
+ typeof value !== "object" ||
+ value instanceof Date ||
+ value instanceof Promise) {
// fun fact: typeof null === 'object'
return value;
}
@@ -1358,7 +1361,6 @@ //------------------------------------------------------------------------------
// Const/global stuff/helpers
//------------------------------------------------------------------------------
- const DISABLED_TAGS = ["input", "textarea", "button", "select", "option", "optgroup"];
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g;
@@ -1371,6 +1373,25 @@ function isComponent(obj) {
return obj && obj.hasOwnProperty("__owl__");
}
+ class VDomArray extends Array {
+ toString() {
+ return vDomToString(this);
+ }
+ }
+ function vDomToString(vdom) {
+ return vdom
+ .map((vnode) => {
+ if (vnode.sel) {
+ const node = document.createElement(vnode.sel);
+ const result = patch(node, vnode);
+ return result.elm.outerHTML;
+ }
+ else {
+ return vnode.text;
+ }
+ })
+ .join("");
+ }
const UTILS = {
zero: Symbol("zero"),
toObj(expr) {
@@ -1392,22 +1413,8 @@ addNameSpace(vnode) {
addNS(vnode.data, vnode.children, vnode.sel);
},
- VDomArray: class VDomArray extends Array {
- },
- vDomToString: function (vdom) {
- return vdom
- .map((vnode) => {
- if (vnode.sel) {
- const node = document.createElement(vnode.sel);
- const result = patch(node, vnode);
- return result.elm.outerHTML;
- }
- else {
- return vnode.text;
- }
- })
- .join("");
- },
+ VDomArray,
+ vDomToString,
getComponent(obj) {
while (obj && !isComponent(obj)) {
obj = obj.__proto__;
@@ -1735,10 +1742,16 @@ }
return;
}
+ if (node.tagName !== "t" && node.hasAttribute("t-call")) {
+ const tCallNode = document.createElement("t");
+ tCallNode.setAttribute("t-call", node.getAttribute("t-call"));
+ node.removeAttribute("t-call");
+ node.prepend(tCallNode);
+ }
const firstLetter = node.tagName[0];
if (firstLetter === firstLetter.toUpperCase()) {
// this is a component, we modify in place the xml document to change
- // <SomeComponent ... /> to <t t-component="SomeComponent" ... />
+ // <SomeComponent ... /> to <SomeComponent t-component="SomeComponent" ... />
node.setAttribute("t-component", node.tagName);
}
else if (node.tagName !== "t" && node.hasAttribute("t-component")) {
@@ -1868,22 +1881,31 @@ const attrs = [];
const props = [];
const tattrs = [];
- function handleBooleanProps(key, val) {
+ function handleProperties(key, val) {
let isProp = false;
- if (node.nodeName === "input" && key === "checked") {
- let type = node.getAttribute("type");
- if (type === "checkbox" || type === "radio") {
- isProp = true;
- }
- }
- if (node.nodeName === "option" && key === "selected") {
- isProp = true;
- }
- if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) {
- isProp = true;
- }
- if ((key === "readonly" && node.nodeName === "input") || node.nodeName === "textarea") {
- isProp = true;
+ switch (node.nodeName) {
+ case "input":
+ let type = node.getAttribute("type");
+ if (type === "checkbox" || type === "radio") {
+ if (key === "checked" || key === "indeterminate") {
+ isProp = true;
+ }
+ }
+ if (key === "value" || key === "readonly" || key === "disabled") {
+ isProp = true;
+ }
+ break;
+ case "option":
+ isProp = key === "selected" || key === "disabled";
+ break;
+ case "textarea":
+ isProp = key === "readonly" || key === "disabled";
+ break;
+ case "button":
+ case "select":
+ case "optgroup":
+ isProp = key === "disabled";
+ break;
}
if (isProp) {
props.push(`${key}: _${val}`);
@@ -1921,7 +1943,7 @@ name = '"' + name + '"';
}
attrs.push(`${name}: _${attID}`);
- handleBooleanProps(name, attID);
+ handleProperties(name, attID);
}
}
// dynamic attributes
@@ -1958,7 +1980,7 @@ }
ctx.addLine(`let _${attID} = ${formattedValue};`);
attrs.push(`${attName}: _${attID}`);
- handleBooleanProps(attName, attID);
+ handleProperties(attName, attID);
}
}
if (name.startsWith("t-attf-")) {
@@ -2279,9 +2301,6 @@ // ------------------------------------------------
ctx.rootContext.shouldDefineScope = true;
ctx.rootContext.shouldDefineUtils = true;
- if (node.nodeName !== "t") {
- throw new Error("Invalid tag for t-call directive (should be 't')");
- }
const subTemplate = node.getAttribute("t-call");
const nodeTemplate = qweb.templates[subTemplate];
if (!nodeTemplate) {
@@ -2299,12 +2318,11 @@ // Step 3: compile t-call body if necessary
// ------------------------------------------------
let hasBody = node.hasChildNodes();
- let protectID;
+ const protectID = ctx.startProtectScope();
if (hasBody) {
// we add a sub scope to protect the ambient scope
ctx.addLine(`{`);
ctx.indent();
- protectID = ctx.startProtectScope();
const nodeCopy = node.cloneNode(true);
for (let attr of ["t-if", "t-else", "t-elif", "t-call"]) {
nodeCopy.removeAttribute(attr);
@@ -2324,28 +2342,27 @@ }
// Step 4: add the appropriate function call to current component
// ------------------------------------------------
- const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)";
const parentComponent = `utils.getComponent(context)`;
const key = ctx.generateTemplateKey();
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
if (ctx.parentNode) {
- ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, ${callingScope}, ${extra});`);
+ ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`);
}
else {
// this is a t-call with no parentnode, we need to extract the result
ctx.rootContext.shouldDefineResult = true;
ctx.addLine(`result = []`);
- ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, ${callingScope}, ${extra});`);
+ ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`);
ctx.addLine(`result = result[0]`);
}
// Step 5: restore previous scope
// ------------------------------------------------
if (hasBody) {
- ctx.stopProtectScope(protectID);
ctx.dedent();
ctx.addLine(`}`);
}
+ ctx.stopProtectScope(protectID);
return true;
},
});
@@ -2653,6 +2670,8 @@ const n = parseFloat(val);
return isNaN(n) ? val : n;
};
+ const hasDotAtTheEnd = /\.[\w_]+\s*$/;
+ const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
QWeb.addDirective({
name: "model",
priority: 42,
@@ -2660,14 +2679,41 @@ const type = node.getAttribute("type");
let handler;
let event = fullName.includes(".lazy") ? "change" : "input";
- // we keep here a reference to the "base expression" (if the expression
- // is `t-model="some.expr.value", then the base expression is "some.expr").
- // This is necessary so we can capture it in the handler closure.
- let expr = ctx.formatExpression(value);
- const index = expr.lastIndexOf(".");
- const baseExpr = expr.slice(0, index);
- ctx.addLine(`let expr${nodeID} = ${baseExpr};`);
- expr = `expr${nodeID}.${expr.slice(index + 1)}`;
+ // First step: we need to understand the structure of the expression, and
+ // from it, extract a base expression (that we can capture, which is
+ // important because it will be used in a handler later) and a formatted
+ // expression (which uses the captured base expression)
+ //
+ // Also, we support 2 kinds of values: some.expr.value or some.expr[value]
+ // For the first one, we have:
+ // - base expression = scope[some].expr
+ // - expression = exprX.value (where exprX is the var that captures the base expr)
+ // and for the expression with brackets:
+ // - base expression = scope[some].expr
+ // - expression = exprX[keyX] (where exprX is the var that captures the base expr
+ // and keyX captures scope[value])
+ let expr;
+ let baseExpr;
+ if (hasDotAtTheEnd.test(value)) {
+ // we manage the case where the expr has a dot: some.expr.value
+ const index = value.lastIndexOf(".");
+ baseExpr = value.slice(0, index);
+ ctx.addLine(`let expr${nodeID} = ${ctx.formatExpression(baseExpr)};`);
+ expr = `expr${nodeID}${value.slice(index)}`;
+ }
+ else if (hasBracketsAtTheEnd.test(value)) {
+ // we manage here the case where the expr ends in a bracket expression:
+ // some.expr[value]
+ const index = value.lastIndexOf("[");
+ baseExpr = value.slice(0, index);
+ ctx.addLine(`let expr${nodeID} = ${ctx.formatExpression(baseExpr)};`);
+ let exprKey = value.trimRight().slice(index + 1, -1);
+ ctx.addLine(`let exprKey${nodeID} = ${ctx.formatExpression(exprKey)};`);
+ expr = `expr${nodeID}[exprKey${nodeID}]`;
+ }
+ else {
+ throw new Error(`Invalid t-model expression: "${value}" (it should be assignable)`);
+ }
const key = ctx.generateTemplateKey();
if (node.tagName === "select") {
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
@@ -3465,13 +3511,21 @@ else {
if (fiber.shouldPatch) {
component.__patch(component.__owl__.vnode, fiber.vnode);
+ // When updating a Component's props (in directive),
+ // the component has a pvnode AND should be patched.
+ // However, its pvnode.elm may have changed if it is a High Order Component
+ if (component.__owl__.pvnode) {
+ component.__owl__.pvnode.elm = component.__owl__.vnode.elm;
+ }
}
else {
component.__patch(document.createElement(fiber.vnode.sel), fiber.vnode);
component.__owl__.pvnode.elm = component.__owl__.vnode.elm;
}
}
- component.__owl__.currentFiber = null;
+ if (fiber === component.__owl__.currentFiber) {
+ component.__owl__.currentFiber = null;
+ }
}
// insert into the DOM (mount case)
let inDOM = false;
@@ -3585,7 +3639,7 @@ throw new Error(`Missing props '${propName}' (component '${Widget.name}')`);
}
else {
- break;
+ continue;
}
}
let isValid;
@@ -4092,9 +4146,9 @@ }
this.willUnmount();
__owl__.isMounted = false;
- if (this.__owl__.currentFiber) {
- this.__owl__.currentFiber.isCompleted = true;
- this.__owl__.currentFiber.root.counter = 0;
+ if (__owl__.currentFiber) {
+ __owl__.currentFiber.isCompleted = true;
+ __owl__.currentFiber.root.counter = 0;
}
const children = __owl__.children;
for (let id in children) {
@@ -4249,9 +4303,28 @@ // destroyed right now, because they are not in the DOM, and thus we won't
// be notified later on (when patching), that they are removed from the DOM
for (let childKey in __owl__.children) {
- let child = __owl__.children[childKey];
- if (!child.__owl__.isMounted && child.__owl__.parentLastFiberId < fiber.id) {
- child.destroy();
+ const child = __owl__.children[childKey];
+ const childOwl = child.__owl__;
+ if (!childOwl.isMounted && childOwl.parentLastFiberId < fiber.id) {
+ // we only do here a "soft" destroy, meaning that we leave the child
+ // dom node alone, without removing it. Most of the time, it does not
+ // matter, because the child component is already unmounted. However,
+ // if some of its parent have been unmounted, the child could actually
+ // still be attached to its parent, and this may be important if we
+ // want to remount the parent, because the vdom need to match the
+ // actual DOM
+ child.__destroy(childOwl.parent);
+ if (childOwl.pvnode) {
+ // we remove the key here to make sure that the patching algorithm
+ // is able to make the difference between this pvnode and an eventual
+ // other instance of the same component
+ delete childOwl.pvnode.key;
+ // Since the component has been unmounted, we do not want to actually
+ // call a remove hook. This is pretty important, since the t-component
+ // directive actually disabled it, so the vdom algorithm will just
+ // not remove the child elm if we don't remove the hook.
+ delete childOwl.pvnode.data.hook.remove;
+ }
}
}
if (!vnode) {
@@ -5195,9 +5268,9 @@ exports.useState = useState$1;
exports.utils = utils;
- exports.__info__.version = '1.0.9';
- exports.__info__.date = '2020-06-09T06:36:43.187Z';
- exports.__info__.hash = 'c5a2f52';
+ exports.__info__.version = '1.0.10';
+ exports.__info__.date = '2020-10-02T07:33:29.701Z';
+ exports.__info__.hash = 'e73fb46';
exports.__info__.url = 'https://github.com/odoo/owl';
}(this.owl = this.owl || {}));
|
