mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] runtime: add support for Map and other iterables in t-foreach
closes: #1352
This commit is contained in:
committed by
Géry Debongnie
parent
7538aeae0e
commit
836e12b1c5
@@ -376,15 +376,16 @@ An important difference should be made with the usual `QWeb` behaviour: Owl
|
|||||||
requires the presence of a `t-key` directive, to be able to properly reconcile
|
requires the presence of a `t-key` directive, to be able to properly reconcile
|
||||||
renderings.
|
renderings.
|
||||||
|
|
||||||
`t-foreach` can iterate on an array (the current item will be the current value)
|
`t-foreach` can iterate on any iterable, and also has special support for objects
|
||||||
or an object (the current item will be the current key).
|
and maps, it will expose the key of the current iteration as the contents of the
|
||||||
|
`t-as`, and the corresponding value with the same name and the suffix `_value`.
|
||||||
|
|
||||||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
In addition to the name passed via t-as, `t-foreach` provides a few other useful
|
||||||
variables for various data points (note: `$as` will be replaced with the name
|
variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||||
passed to `t-as`):
|
|
||||||
|
|
||||||
- `$as_value`: the current iteration value, identical to `$as` for lists and
|
- `$as_value`: the current iteration value, identical to `$as` for arrays and
|
||||||
integers, but for objects, it provides the value (where `$as` provides the key)
|
other iterables, but for objects and maps, it provides the value (where `$as`
|
||||||
|
provides the key)
|
||||||
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
||||||
- `$as_first`: whether the current item is the first of the iteration
|
- `$as_first`: whether the current item is the first of the iteration
|
||||||
(equivalent to `$as_index == 0`)
|
(equivalent to `$as_index == 0`)
|
||||||
|
|||||||
@@ -60,18 +60,26 @@ function withKey(elem: any, k: string) {
|
|||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareList(collection: any): [any[], any[], number, any[]] {
|
function prepareList(collection: unknown): [unknown[], unknown[], number, undefined[]] {
|
||||||
let keys: any[];
|
let keys: unknown[];
|
||||||
let values: any[];
|
let values: unknown[];
|
||||||
|
|
||||||
if (Array.isArray(collection)) {
|
if (Array.isArray(collection)) {
|
||||||
keys = collection;
|
keys = collection;
|
||||||
values = collection;
|
values = collection;
|
||||||
} else if (collection) {
|
} else if (collection instanceof Map) {
|
||||||
values = Object.keys(collection);
|
keys = [...collection.keys()];
|
||||||
keys = Object.values(collection);
|
values = [...collection.values()];
|
||||||
|
} else if (collection && typeof collection === "object") {
|
||||||
|
if (Symbol.iterator in collection) {
|
||||||
|
keys = [...(<Iterable<unknown>>collection)];
|
||||||
|
values = keys;
|
||||||
|
} else {
|
||||||
|
values = Object.keys(collection);
|
||||||
|
keys = Object.values(collection);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new OwlError("Invalid loop expression");
|
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||||
}
|
}
|
||||||
const n = values.length;
|
const n = values.length;
|
||||||
return [keys, values, n, new Array(n)];
|
return [keys, values, n, new Array(n)];
|
||||||
|
|||||||
@@ -77,6 +77,62 @@ exports[`t-foreach iterate on items 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, Map param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, Set param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-foreach iterate, dict param 1`] = `
|
exports[`t-foreach iterate, dict param 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -108,6 +164,62 @@ exports[`t-foreach iterate, dict param 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, generator param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['gen']());;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-foreach iterate, iterable param 1`] = `
|
||||||
|
"function anonymous(app, bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
let { prepareList, withKey } = helpers;
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
ctx = Object.create(ctx);
|
||||||
|
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['map'].values());;
|
||||||
|
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||||
|
ctx[\`item\`] = v_block1[i1];
|
||||||
|
ctx[\`item_index\`] = i1;
|
||||||
|
ctx[\`item_value\`] = k_block1[i1];
|
||||||
|
const key1 = ctx['item_index'];
|
||||||
|
const b3 = text(\` [\`);
|
||||||
|
const b4 = text(ctx['item_index']);
|
||||||
|
const b5 = text(\`: \`);
|
||||||
|
const b6 = text(ctx['item']);
|
||||||
|
const b7 = text(\` \`);
|
||||||
|
const b8 = text(ctx['item_value']);
|
||||||
|
const b9 = text(\`] \`);
|
||||||
|
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||||
|
}
|
||||||
|
return list(c_block1);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-foreach iterate, position 1`] = `
|
exports[`t-foreach iterate, position 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -105,6 +105,64 @@ describe("t-foreach", () => {
|
|||||||
expect(renderToString(template, context)).toBe(expected);
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("iterate, Map param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 a] [1: 2 b] [2: 3 c] `;
|
||||||
|
const context = {
|
||||||
|
value: new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2],
|
||||||
|
["c", 3],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iterate, Set param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||||
|
const context = { value: new Set([1, 2, 3]) };
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iterate, iterable param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="map.values()" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||||
|
const context = {
|
||||||
|
map: new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2],
|
||||||
|
["c", 3],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iterate, generator param", () => {
|
||||||
|
const template = `
|
||||||
|
<t t-foreach="gen()" t-as="item" t-key="item_index">
|
||||||
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
|
</t>`;
|
||||||
|
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||||
|
const context = {
|
||||||
|
*gen() {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
yield 3;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(renderToString(template, context)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
test("does not pollute the rendering context", () => {
|
test("does not pollute the rendering context", () => {
|
||||||
const template = `
|
const template = `
|
||||||
<div>
|
<div>
|
||||||
@@ -193,7 +251,9 @@ describe("t-foreach", () => {
|
|||||||
|
|
||||||
test("throws error if invalid loop expression", () => {
|
test("throws error if invalid loop expression", () => {
|
||||||
const test = `<div><t t-foreach="abc" t-as="item" t-key="item"><span t-key="item_index"/></t></div>`;
|
const test = `<div><t t-foreach="abc" t-as="item" t-key="item"><span t-key="item_index"/></t></div>`;
|
||||||
expect(() => renderToString(test)).toThrow("Invalid loop expression");
|
expect(() => renderToString(test)).toThrow(
|
||||||
|
'Invalid loop expression: "undefined" is not iterable'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-foreach with t-if inside", () => {
|
test("t-foreach with t-if inside", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user