Compare commits

...

9 Commits

Author SHA1 Message Date
Géry Debongnie 9fe2da704e [REL] v1.4.3
# v1.4.3

- fix: another scoping issue with t-slots
2021-07-08 11:54:32 +02:00
Géry Debongnie 21b1661d39 [CLEANUP] run prettier on codebase 2021-07-08 11:53:48 +02:00
Géry Debongnie ec05b1f5e3 [FIX] slots: fix bad interaction between t-call and slots
The previous changes in the combine method (used to copy all the
variables defined in the current scope for use in a slot) had the effect
of squashing the prototype chain: instead of `Component -> Obj1 ->
Obj2 -> Obj3`, the combined scope had: `Component -> Obj1'`.

This has an unfortunate interaction with the way t-call is implemented,
which uses the fact that we are in a subscope to add a own
__access_mode__. It depends specifially on the prototype chain, and that
the parent scope may have a different value for that property. But with
the way combine was implemented, we lost all that subtlety since
everything is squashed.

In this commit, we reimplement that function in a way to make sure we
keep the prototype chain structure
2021-07-08 11:49:25 +02:00
Géry Debongnie 8083678f03 [REL] v1.4.2
# 1.4.2

- qweb: properly handle inline expressions with lists such as '[a,b,c]'
2021-07-07 14:56:46 +02:00
Géry Debongnie 61c2ec5d83 [FIX] qweb: properly handle lists in inline expressions
Before this commit, Owl inline expressions with a list with multiple
elements such as [a,b,c] was transformed into

[scope['a'], b: scope['b'], scope['c']]

instead of

[scope['a'], scope['b'], scope['c']]

This is due to a previous commit adding support for short object
descriptions such as {a,b}.

To fix this means that we have to keep track of the current group type
for the expression, which is done by using a stack.
2021-07-07 14:50:27 +02:00
Géry Debongnie e579a993fd [REL] v1.4.1
# 1.4.1

This release brings in two small fixes:

- vdom: a performance improvement to reduce number of calls to classList.remove
- slots: fix subtle issue with wrong context used in event handlers when
  multiple slots are involved
2021-07-07 11:10:05 +02:00
Géry Debongnie ea3c6f7bf0 [FIX] slots: make sure handlers are properly bound to component
In some situations (a slot inside a slot), the combine method was
wrongly copying all properties of the scope into the context, which
caused the event handling system to wrongly use a subobject as component
(since it detects the fact that __owl__ is a own property_).
Consequently, we could have very subtle issue with some properties being
shadowed by a sub object.
2021-07-07 11:04:25 +02:00
Pierre Paridans 9cfafc30b5 [IMP] vdom: performance improvement
Port from original snabbdom project: snabbdom/snabbdom#634

The issue is that before this commit, the removeClass method was
sometimes called even if it is not useful. See this comment for more
detail: https://github.com/snabbdom/snabbdom/issues/633#issue-618706258
2021-07-06 14:14:40 +02:00
Aaron Bohy ad42c583c6 [FIX] package.json: bump node version to v12
Node 10.x is no longer maintained, and since a recent commit [1],
a test fails with that version.

[1] 88fd1cf483
2021-07-06 13:30:17 +02:00
9 changed files with 167 additions and 10 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [12.x, 14.x]
steps:
- uses: actions/checkout@v2
+1 -1
View File
@@ -120,7 +120,7 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.4.0](https://github.com/odoo/owl/releases/tag/v1.4.0)
- [owl-1.4.3](https://github.com/odoo/owl/releases/tag/v1.4.3)
## License
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "1.4.0",
"version": "1.4.3",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
@@ -10,7 +10,7 @@
"dist"
],
"engines": {
"node": ">=10.15.3"
"node": ">=12.18.3"
},
"scripts": {
"build:bundle": "rollup -c",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.4.0
- Current version: 1.4.3
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+18 -1
View File
@@ -257,16 +257,33 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
const tokens = tokenize(expr);
let i = 0;
let stack = []; // to track last opening [ or {
while (i < tokens.length) {
let token = tokens[i];
let prevToken = tokens[i - 1];
let nextToken = tokens[i + 1];
let groupType = stack[stack.length - 1];
switch (token.type) {
case "LEFT_BRACE":
case "LEFT_BRACKET":
stack.push(token.type);
break;
case "RIGHT_BRACE":
case "RIGHT_BRACKET":
stack.pop();
}
let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
if (prevToken) {
// normalize missing tokens: {a} should be equivalent to {a:a}
if (isLeftSeparator(prevToken) && isRightSeparator(nextToken)) {
if (
groupType === "LEFT_BRACE" &&
isLeftSeparator(prevToken) &&
isRightSeparator(nextToken)
) {
tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
nextToken = tokens[i + 1];
}
+18 -3
View File
@@ -137,10 +137,25 @@ const UTILS: Utils = {
}
return result;
},
/**
* This method combines the current context with the variables defined in a
* scope for use in a slot.
*
* The implementation is kind of tricky because we want to preserve the
* prototype chain structure of the cloned result. So we need to traverse the
* prototype chain, cloning each level respectively.
*/
combine(context, scope) {
const clone = Object.create(context);
for (let k in scope) {
clone[k] = scope[k];
let clone = context;
const scopeStack = [];
while (!isComponent(scope)) {
scopeStack.push(scope);
scope = scope.__proto__;
}
while (scopeStack.length) {
let scope = scopeStack.pop();
clone = Object.create(clone);
Object.assign(clone, scope);
}
return clone;
},
+2 -1
View File
@@ -231,7 +231,8 @@ function updateClass(oldVnode: VNode, vnode: VNode): void {
elm = vnode.elm as Element;
for (name in oldClass) {
if (name && !klass[name]) {
if (name && !klass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) {
// was `true` and now not provided
elm.classList.remove(name);
}
}
+113
View File
@@ -1257,4 +1257,117 @@ describe("t-slot directive", () => {
`<div><div class="slotted"><div class="slot"><div class="child"></div></div></div></div>`
);
});
test("t-slot scope context", async () => {
expect.assertions(1);
class Wrapper extends Component {
static template = xml`<t t-slot="default"/>`;
}
let dialog;
class Dialog extends Component {
static template = xml`
<Wrapper>
<div t-on-click="onClick">
<t t-slot="default" />
</div>
</Wrapper>
`;
static components = { Wrapper };
setup() {
dialog = this;
}
onClick(ev) {
// we do not use expect(this).toBe(dialog) here because if it fails, it
// may blow up jest because it then tries to compute a diff, which is
// infinite if there is a cycle
expect(this === dialog).toBe(true);
}
}
class Parent extends Component {
static template = xml`
<Dialog>
<button>The Button</button>
</Dialog>`;
static components = { Dialog };
}
await mount(Parent, { target: fixture });
document.querySelector("button").click();
await nextTick();
});
test("t-slot in recursive templates", async () => {
QWeb.registerTemplate(
"_test_recursive_template",
`
<Wrapper>
<t t-esc="name" />
<t t-foreach="items" t-as="item">
<t t-if="!item.children.length">
<t t-esc="item.name" />
</t>
<t t-else="" t-call="_test_recursive_template">
<t t-set="name" t-value="item.name" />
<t t-set="items" t-value="item.children" />
</t>
</t>
</Wrapper>`
);
class Wrapper extends Component {
static template = xml`
<wrapper>
<t t-slot="default"/>
</wrapper>`;
}
class Parent extends Component {
static template = "_test_recursive_template";
static components = { Wrapper };
name = "foo";
items = [
{
name: "foo-0",
children: [
{ name: "foo-00", children: [] },
{
name: "foo-01",
children: [
{ name: "foo-010", children: [] },
{ name: "foo-011", children: [] },
{
name: "foo-012",
children: [
{ name: "foo-0120", children: [] },
{ name: "foo-0121", children: [] },
{ name: "foo-0122", children: [] },
],
},
],
},
{ name: "foo-02", children: [] },
],
},
{ name: "foo-1", children: [] },
{ name: "foo-2", children: [] },
];
}
await mount(Parent, { target: fixture });
expect(fixture.innerHTML).toBe(
"<wrapper>foo<wrapper>foo-0foo-00<wrapper>foo-01foo-010foo-011<wrapper>foo-012foo-0120foo-0121foo-0122</wrapper></wrapper>foo-02</wrapper>foo-1foo-2</wrapper>"
);
});
});
+11
View File
@@ -207,6 +207,17 @@ describe("expression evaluation", () => {
expect(compileExpr("{a,b:3,c}", {})).toBe("{a:scope['a'],b:3,c:scope['c']}");
});
test("works with short object description and lists ", () => {
expect(compileExpr("[a, b]", {})).toBe("[scope['a'],scope['b']]");
expect(compileExpr("[a, b, c]", {})).toBe("[scope['a'],scope['b'],scope['c']]");
expect(compileExpr("[a, {b, c},d]", {})).toBe(
"[scope['a'],{b:scope['b'],c:scope['c']},scope['d']]"
);
expect(compileExpr("{a:[b, {c, d: e}]}", {})).toBe(
"{a:[scope['b'],{c:scope['c'],d:scope['e']}]}"
);
});
test("template strings", () => {
expect(compileExpr("`hey`", {})).toBe("`hey`");
expect(compileExpr("`hey ${you}`", {})).toBe("`hey ${scope['you']}`");