Compare commits

..

2 Commits

Author SHA1 Message Date
Géry Debongnie 4cffb23bd5 [REF] blockdom: some small optimization 2023-07-18 13:30:45 +02:00
Géry Debongnie 5154a1cc1d [REF] blockdom: micro optimizations 2023-07-17 15:01:42 +02:00
15 changed files with 180 additions and 358 deletions
+7 -8
View File
@@ -376,16 +376,15 @@ 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
renderings.
`t-foreach` can iterate on any iterable, and also has special support for objects
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`.
`t-foreach` can iterate on an array (the current item will be the current value)
or an object (the current item will be the current key).
In addition to the name passed via t-as, `t-foreach` provides a few other useful
variables (note: `$as` will be replaced with the name passed to `t-as`):
In addition to the name passed via t-as, `t-foreach` provides a few other
variables for various data points (note: `$as` will be replaced with the name
passed to `t-as`):
- `$as_value`: the current iteration value, identical to `$as` for arrays and
other iterables, but for objects and maps, it provides the value (where `$as`
provides the key)
- `$as_value`: the current iteration value, identical to `$as` for lists and
integers, but for objects, 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_first`: whether the current item is the first of the iteration
(equivalent to `$as_index == 0`)
+66 -98
View File
@@ -122,16 +122,14 @@ function handleError(params) {
}
const node = "node" in params ? params.node : params.fiber.node;
const fiber = "fiber" in params ? params.fiber : node.fiber;
if (fiber) {
// resets the fibers on components if possible. This is important so that
// new renderings can be properly included in the initial one, if any.
let current = fiber;
do {
current.node.fiber = current;
current = current.parent;
} while (current);
fibersInError.set(fiber.root, error);
}
// resets the fibers on components if possible. This is important so that
// new renderings can be properly included in the initial one, if any.
let current = fiber;
do {
current.node.fiber = current;
current = current.parent;
} while (current);
fibersInError.set(fiber.root, error);
const handled = _handleError(node, error);
if (!handled) {
console.warn(`[Owl] Unhandled error. Destroying the root component`);
@@ -313,13 +311,20 @@ function updateClass(val, oldVal) {
* @returns a batched version of the original callback
*/
function batched(callback) {
let scheduled = false;
return async (...args) => {
if (!scheduled) {
scheduled = true;
await Promise.resolve();
scheduled = false;
callback(...args);
let called = false;
return async () => {
// This await blocks all calls to the callback here, then releases them sequentially
// in the next microtick. This line decides the granularity of the batch.
await Promise.resolve();
if (!called) {
called = true;
// wait for all calls in this microtick to fall through before resetting "called"
// so that only the first call to the batched function calls the original callback.
// Schedule this before calling the callback so that calls to the batched function
// within the callback will proceed only after resetting called to false, and have
// a chance to execute the callback again
Promise.resolve().then(() => (called = false));
callback();
}
};
}
@@ -1652,7 +1657,8 @@ function cancelFibers(fibers) {
let node = fiber.node;
fiber.render = throwOnRender;
if (node.status === 0 /* NEW */) {
node.cancel();
node.destroy();
delete node.parent.children[node.parentKey];
}
node.fiber = null;
if (fiber.bdom) {
@@ -2379,9 +2385,6 @@ class ComponentNode {
}
}
async render(deep) {
if (this.status >= 2 /* CANCELLED */) {
return;
}
let current = this.fiber;
if (current && (current.root.locked || current.bdom === true)) {
await Promise.resolve();
@@ -2407,7 +2410,7 @@ class ComponentNode {
this.fiber = fiber;
this.app.scheduler.addFiber(fiber);
await Promise.resolve();
if (this.status >= 2 /* CANCELLED */) {
if (this.status === 2 /* DESTROYED */) {
return;
}
// We only want to actually render the component if the following two
@@ -2425,18 +2428,6 @@ class ComponentNode {
fiber.render();
}
}
cancel() {
this._cancel();
delete this.parent.children[this.parentKey];
this.app.scheduler.scheduleDestroy(this);
}
_cancel() {
this.status = 2 /* CANCELLED */;
const children = this.children;
for (let childKey in children) {
children[childKey]._cancel();
}
}
destroy() {
let shouldRemove = this.status === 1 /* MOUNTED */;
this._destroy();
@@ -2464,7 +2455,7 @@ class ComponentNode {
this.app.handleError({ error: e, node: this });
}
}
this.status = 3 /* DESTROYED */;
this.status = 2 /* DESTROYED */;
}
async updateAndRender(props, parentFiber) {
this.nextProps = props;
@@ -2997,22 +2988,12 @@ function prepareList(collection) {
keys = collection;
values = collection;
}
else if (collection instanceof Map) {
keys = [...collection.keys()];
values = [...collection.values()];
}
else if (collection && typeof collection === "object") {
if (Symbol.iterator in collection) {
keys = [...collection];
values = keys;
}
else {
values = Object.keys(collection);
keys = Object.values(collection);
}
else if (collection) {
values = Object.keys(collection);
keys = Object.values(collection);
}
else {
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
throw new OwlError("Invalid loop expression");
}
const n = values.length;
return [keys, values, n, new Array(n)];
@@ -4839,10 +4820,10 @@ function parseNode(node, ctx) {
parseTCall(node, ctx) ||
parseTCallBlock(node) ||
parseTEscNode(node, ctx) ||
parseTOutNode(node, ctx) ||
parseTKey(node, ctx) ||
parseTTranslation(node, ctx) ||
parseTSlot(node, ctx) ||
parseTOutNode(node, ctx) ||
parseComponent(node, ctx) ||
parseDOMNode(node, ctx) ||
parseTSetNode(node, ctx) ||
@@ -4955,8 +4936,10 @@ function parseDOMNode(node, ctx) {
const typeAttr = node.getAttribute("type");
const isInput = tagName === "input";
const isSelect = tagName === "select";
const isTextarea = tagName === "textarea";
const isCheckboxInput = isInput && typeAttr === "checkbox";
const isRadioInput = isInput && typeAttr === "radio";
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
const hasLazyMod = attr.includes(".lazy");
const hasNumberMod = attr.includes(".number");
const hasTrimMod = attr.includes(".trim");
@@ -4968,8 +4951,8 @@ function parseDOMNode(node, ctx) {
specialInitTargetAttr: isRadioInput ? "checked" : null,
eventType,
hasDynamicChildren: false,
shouldTrim: hasTrimMod,
shouldNumberize: hasNumberMod,
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
};
if (isSelect) {
// don't pollute the original ctx
@@ -5032,6 +5015,9 @@ function parseTEscNode(node, ctx) {
content: [tesc],
};
}
if (ast.type === 11 /* TComponent */) {
throw new OwlError("t-esc is not supported on Component nodes");
}
return tesc;
}
// -----------------------------------------------------------------------------
@@ -5473,21 +5459,19 @@ function normalizeTIf(el) {
*
* @param el the element containing the tree that should be normalized
*/
function normalizeTEscTOut(el) {
for (const d of ["t-esc", "t-out"]) {
const elements = [...el.querySelectorAll(`[${d}]`)].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
for (const el of elements) {
if (el.childNodes.length) {
throw new OwlError(`Cannot have ${d} on a component that already has content`);
}
const value = el.getAttribute(d);
el.removeAttribute(d);
const t = el.ownerDocument.createElement("t");
if (value != null) {
t.setAttribute(d, value);
}
el.appendChild(t);
function normalizeTEsc(el) {
const elements = [...el.querySelectorAll("[t-esc]")].filter((el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component"));
for (const el of elements) {
if (el.childNodes.length) {
throw new OwlError("Cannot have t-esc on a component that already has content");
}
const value = el.getAttribute("t-esc");
el.removeAttribute("t-esc");
const t = el.ownerDocument.createElement("t");
if (value != null) {
t.setAttribute("t-esc", value);
}
el.appendChild(t);
}
}
/**
@@ -5498,7 +5482,7 @@ function normalizeTEscTOut(el) {
*/
function normalizeXML(el) {
normalizeTIf(el);
normalizeTEscTOut(el);
normalizeTEsc(el);
}
/**
* Parses an XML string into an XML document, throwing errors on parser errors
@@ -5551,7 +5535,7 @@ function compile(template, options = {}) {
}
// do not modify manually. This file is generated by the release script.
const version = "2.2";
const version = "2.1.3";
// -----------------------------------------------------------------------------
// Scheduler
@@ -5561,18 +5545,11 @@ class Scheduler {
this.tasks = new Set();
this.frame = 0;
this.delayedRenders = [];
this.cancelledNodes = new Set();
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
}
addFiber(fiber) {
this.tasks.add(fiber.root);
}
scheduleDestroy(node) {
this.cancelledNodes.add(node);
if (this.frame === 0) {
this.frame = this.requestAnimationFrame(() => this.processTasks());
}
}
/**
* Process all current tasks. This only applies to the fibers that are ready.
* Other tasks are left unchanged.
@@ -5582,28 +5559,21 @@ class Scheduler {
let renders = this.delayedRenders;
this.delayedRenders = [];
for (let f of renders) {
if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
f.render();
}
}
}
if (this.frame === 0) {
this.frame = this.requestAnimationFrame(() => this.processTasks());
}
}
processTasks() {
this.frame = 0;
for (let node of this.cancelledNodes) {
node._destroy();
}
this.cancelledNodes.clear();
for (let task of this.tasks) {
this.processFiber(task);
}
for (let task of this.tasks) {
if (task.node.status === 3 /* DESTROYED */) {
this.tasks.delete(task);
}
this.frame = this.requestAnimationFrame(() => {
this.frame = 0;
this.tasks.forEach((fiber) => this.processFiber(fiber));
for (let task of this.tasks) {
if (task.node.status === 2 /* DESTROYED */) {
this.tasks.delete(task);
}
}
});
}
}
processFiber(fiber) {
@@ -5616,7 +5586,7 @@ class Scheduler {
this.tasks.delete(fiber);
return;
}
if (fiber.node.status === 3 /* DESTROYED */) {
if (fiber.node.status === 2 /* DESTROYED */) {
this.tasks.delete(fiber);
return;
}
@@ -5840,11 +5810,9 @@ function status(component) {
switch (component.__owl__.status) {
case 0 /* NEW */:
return "new";
case 2 /* CANCELLED */:
return "cancelled";
case 1 /* MOUNTED */:
return "mounted";
case 3 /* DESTROYED */:
case 2 /* DESTROYED */:
return "destroyed";
}
}
@@ -5984,6 +5952,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
__info__.date = '2023-07-19T13:22:24.480Z';
__info__.hash = '2e07799';
__info__.date = '2023-06-28T09:17:13.630Z';
__info__.hash = '432ff44';
__info__.url = 'https://github.com/odoo/owl';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.2.1",
"version": "2.1.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.2.1",
"version": "2.1.4",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
+19 -18
View File
@@ -235,10 +235,10 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
parseTCall(node, ctx) ||
parseTCallBlock(node, ctx) ||
parseTEscNode(node, ctx) ||
parseTOutNode(node, ctx) ||
parseTKey(node, ctx) ||
parseTTranslation(node, ctx) ||
parseTSlot(node, ctx) ||
parseTOutNode(node, ctx) ||
parseComponent(node, ctx) ||
parseDOMNode(node, ctx) ||
parseTSetNode(node, ctx) ||
@@ -444,6 +444,9 @@ function parseTEscNode(node: Element, ctx: ParsingContext): AST | null {
content: [tesc],
};
}
if (ast.type === ASTType.TComponent) {
throw new OwlError("t-esc is not supported on Component nodes");
}
return tesc;
}
@@ -938,23 +941,21 @@ function normalizeTIf(el: Element) {
*
* @param el the element containing the tree that should be normalized
*/
function normalizeTEscTOut(el: Element) {
for (const d of ["t-esc", "t-out"]) {
const elements = [...el.querySelectorAll(`[${d}]`)].filter(
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
);
for (const el of elements) {
if (el.childNodes.length) {
throw new OwlError(`Cannot have ${d} on a component that already has content`);
}
const value = el.getAttribute(d);
el.removeAttribute(d);
const t = el.ownerDocument.createElement("t");
if (value != null) {
t.setAttribute(d, value);
}
el.appendChild(t);
function normalizeTEsc(el: Element) {
const elements = [...el.querySelectorAll("[t-esc]")].filter(
(el) => el.tagName[0] === el.tagName[0].toUpperCase() || el.hasAttribute("t-component")
);
for (const el of elements) {
if (el.childNodes.length) {
throw new OwlError("Cannot have t-esc on a component that already has content");
}
const value = el.getAttribute("t-esc");
el.removeAttribute("t-esc");
const t = el.ownerDocument.createElement("t");
if (value != null) {
t.setAttribute("t-esc", value);
}
el.appendChild(t);
}
}
@@ -966,7 +967,7 @@ function normalizeTEscTOut(el: Element) {
*/
function normalizeXML(el: Element) {
normalizeTIf(el);
normalizeTEscTOut(el);
normalizeTEsc(el);
}
/**
+59 -19
View File
@@ -93,6 +93,17 @@ function normalizeNode(node: HTMLElement | Text) {
}
}
/**
* Encode 2 numbers and 1 boolean in a number, using 31 bits:
* n1 => encoded in 16 most significant bits
* n2 => encoded in 15 next bits
* boolean => encoded in last significant bit.
* This code assumes that n1 and n2 are small enough to fit in that number of bits
*/
function encodeValue(n1: number, n2: number, b: boolean): number {
return (((n1 << 15) | n2) << 1) | (b ? 1 : 0);
}
// -----------------------------------------------------------------------------
// building a intermediate tree
// -----------------------------------------------------------------------------
@@ -306,7 +317,7 @@ interface IndexedLocation extends Location {
interface Child {
parentRefIdx: number;
afterRefIdx?: number;
afterRefIdx: number;
isOnlyChild?: boolean;
}
@@ -374,6 +385,7 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
// tree is the parentnode here
ctx.children[info.idx] = {
parentRefIdx: info.refIdx!,
afterRefIdx: 0,
isOnlyChild: true,
};
} else {
@@ -501,7 +513,6 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
}));
const locN = locations.length;
const childN = children.length;
const childrenLocs = children;
const isDynamic = refN > 0;
// these values are defined here to make them faster to lookup in the class
@@ -556,6 +567,19 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
}
if (isDynamic) {
const nextSibling = nodeGetNextSibling;
const firstChild = nodeGetFirstChild;
const bitPackedCollectors = new Uint32Array(
collectors.map((c) => {
return encodeValue(c.idx, c.prevIdx, c.getVal === nextSibling);
})
);
const childrenLocs = new Uint32Array(
children.map((c) => {
return encodeValue(c.afterRefIdx, c.parentRefIdx, Boolean(c.isOnlyChild));
})
);
Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
const el = nodeCloneNode.call(template, true);
// collecting references
@@ -563,12 +587,17 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
this.refs = refs;
refs[0] = el;
for (let i = 0; i < colN; i++) {
const w = collectors[i];
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
let info = bitPackedCollectors[i];
// decode info
const fn = (info & 1) === 1 ? nextSibling : firstChild;
info = info >> 1;
const prevIdx = info & 0b111111111111111;
const idx = info >> 15;
refs[idx] = fn.call(refs[prevIdx]);
}
// applying data to all update points
if (locN) {
if (locN !== 0) {
const data = this.data!;
for (let i = 0; i < locN; i++) {
const loc = locations[i];
@@ -579,15 +608,21 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
nodeInsertBefore.call(parent, el, afterNode);
// preparing all children
if (childN) {
if (childN !== 0) {
const children = this.children;
for (let i = 0; i < childN; i++) {
const child = children![i];
if (child) {
const loc = childrenLocs[i];
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
child.isOnlyChild = loc.isOnlyChild;
child.mount(refs[loc.parentRefIdx] as any, afterNode);
if (child !== undefined) {
let info = childrenLocs[i];
// decode info
const isOnlyChild = info & 1;
info = info >> 1;
const parentRefIdx = info & 0b111111111111111;
const afterRefIdx = info >> 15;
const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null;
child.isOnlyChild = isOnlyChild as any;
child.mount(refs[parentRefIdx] as any, afterNode);
}
}
}
@@ -601,7 +636,7 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
}
const refs = this.refs!;
// update texts/attributes/
if (locN) {
if (locN !== 0) {
const data1 = this.data!;
const data2 = other.data!;
for (let i = 0; i < locN; i++) {
@@ -616,14 +651,14 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
}
// update children
if (childN) {
if (childN !== 0) {
let children1 = this.children;
const children2 = other.children;
for (let i = 0; i < childN; i++) {
const child1 = children1![i];
const child2 = children2![i];
if (child1) {
if (child2) {
if (child1 !== undefined) {
if (child2 !== undefined) {
child1.patch(child2, withBeforeRemove);
} else {
if (withBeforeRemove) {
@@ -632,10 +667,15 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
child1.remove();
children1![i] = undefined;
}
} else if (child2) {
const loc = childrenLocs[i];
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
child2.mount(refs[loc.parentRefIdx] as any, afterNode);
} else if (child2 !== undefined) {
let info = childrenLocs[i];
// decode info
info = info >> 1;
const parentRefIdx = info & 0b111111111111111;
const afterRefIdx = info >> 15;
const afterNode = afterRefIdx !== 0 ? refs[afterRefIdx] : null;
child2.mount(refs[parentRefIdx] as any, afterNode);
children1![i] = child2;
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ function createElementHandler(evName: string, capture: boolean = false): EventHa
}
function remove(this: HTMLElement) {
delete (this as any)[eventKey];
(this as any)[eventKey] = false;
this.removeEventListener(evName, listener, { capture });
}
function update(this: HTMLElement, data: any) {
+5 -5
View File
@@ -28,7 +28,7 @@ class VList {
this.anchor = _anchor;
nodeInsertBefore.call(parent, _anchor, afterNode);
const l = children.length;
if (l) {
if (l !== 0) {
const mount = children[0].mount;
for (let i = 0; i < l; i++) {
mount.call(children[i], parent, _anchor);
@@ -186,7 +186,7 @@ class VList {
} else {
for (let i = startIdx1; i <= endIdx1; i++) {
let ch = ch1[i];
if (ch) {
if (ch !== null) {
if (withBeforeRemove) {
beforeRemove.call(ch);
}
@@ -200,7 +200,7 @@ class VList {
beforeRemove() {
const children = this.children;
const l = children.length;
if (l) {
if (l !== 0) {
const beforeRemove = children[0].beforeRemove;
for (let i = 0; i < l; i++) {
beforeRemove.call(children[i]);
@@ -215,7 +215,7 @@ class VList {
} else {
const children = this.children;
const l = children.length;
if (l) {
if (l !== 0) {
const remove = children[0].remove;
for (let i = 0; i < l; i++) {
remove.call(children[i]);
@@ -240,7 +240,7 @@ export function list(children: VNode[]): VNode<VList> {
}
function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
let mapping: any = {};
const mapping: any = {};
for (let i = startIdx1; i <= endIdx2; i++) {
mapping[ch1[i].key] = i;
}
+10 -10
View File
@@ -26,7 +26,7 @@ export class VMulti {
const anchors = new Array(l);
for (let i = 0; i < l; i++) {
let child = children[i];
if (child) {
if (child !== undefined) {
child.mount(parent, afterNode);
} else {
const childAnchor = document.createTextNode("");
@@ -44,7 +44,7 @@ export class VMulti {
const anchors = this.anchors;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
if (child) {
if (child !== undefined) {
child.moveBeforeDOMNode(node, parent);
} else {
const anchor = anchors![i];
@@ -56,14 +56,14 @@ export class VMulti {
moveBeforeVNode(other: VMulti | null, afterNode: Node | null) {
if (other) {
const next = other!.children[0];
afterNode = (next ? next.firstNode() : other!.anchors![0]) || null;
afterNode = (next !== undefined ? next.firstNode() : other!.anchors![0]) || null;
}
const children = this.children;
const parent = this.parentEl;
const anchors = this.anchors;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
if (child) {
if (child !== undefined) {
child.moveBeforeVNode(null, afterNode);
} else {
const anchor = anchors![i];
@@ -83,8 +83,8 @@ export class VMulti {
for (let i = 0, l = children1.length; i < l; i++) {
const vn1 = children1[i];
const vn2 = children2[i];
if (vn1) {
if (vn2) {
if (vn1 !== undefined) {
if (vn2 !== undefined) {
vn1.patch(vn2, withBeforeRemove);
} else {
const afterNode = vn1.firstNode()!;
@@ -97,7 +97,7 @@ export class VMulti {
vn1.remove();
children1[i] = undefined;
}
} else if (vn2) {
} else if (vn2 !== undefined) {
children1[i] = vn2;
const anchor = anchors[i];
vn2.mount(parentEl, anchor);
@@ -110,7 +110,7 @@ export class VMulti {
const children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i];
if (child) {
if (child !== undefined) {
child.beforeRemove();
}
}
@@ -125,7 +125,7 @@ export class VMulti {
const anchors = this.anchors;
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i];
if (child) {
if (child !== undefined) {
child.remove();
} else {
nodeRemoveChild.call(parentEl, anchors![i]);
@@ -136,7 +136,7 @@ export class VMulti {
firstNode(): Node | undefined {
const child = this.children[0];
return child ? child.firstNode() : this.anchors![0];
return child !== undefined ? child.firstNode() : this.anchors![0];
}
toString(): string {
+8 -16
View File
@@ -30,7 +30,7 @@ function callSlot(
const slots = ctx.props.slots || {};
const { __render, __ctx, __scope } = slots[name] || {};
const slotScope = ObjectCreate(__ctx || {});
if (__scope) {
if (__scope !== undefined) {
slotScope[__scope] = extra;
}
const slotBDom = __render ? __render(slotScope, parent, key) : null;
@@ -60,26 +60,18 @@ function withKey(elem: any, k: string) {
return elem;
}
function prepareList(collection: unknown): [unknown[], unknown[], number, undefined[]] {
let keys: unknown[];
let values: unknown[];
function prepareList(collection: any): [any[], any[], number, any[]] {
let keys: any[];
let values: any[];
if (Array.isArray(collection)) {
keys = collection;
values = collection;
} else if (collection instanceof Map) {
keys = [...collection.keys()];
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 if (collection) {
values = Object.keys(collection);
keys = Object.values(collection);
} else {
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
throw new OwlError("Invalid loop expression");
}
const n = values.length;
return [keys, values, n, new Array(n)];
+1 -1
View File
@@ -11,7 +11,7 @@ export type Callback = () => void;
export function batched(callback: Callback): Callback {
let scheduled = false;
return async (...args) => {
if (!scheduled) {
if (scheduled === false) {
scheduled = true;
await Promise.resolve();
scheduled = false;
+1 -1
View File
@@ -1,2 +1,2 @@
// do not modify manually. This file is generated by the release script.
export const version = "2.2.1";
export const version = "2.1.4";
@@ -77,62 +77,6 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
@@ -164,62 +108,6 @@ 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`] = `
"function anonymous(app, bdom, helpers
) {
-6
View File
@@ -1569,12 +1569,6 @@ describe("qweb parser", () => {
);
});
test("component with t-out", async () => {
expect(parse(`<MyComponent t-out="someValue"/>`)).toEqual(
parse(`<MyComponent><t t-out="someValue"/></MyComponent>`)
);
});
test("component with t-esc and content", async () => {
expect(() => parse(`<MyComponent t-esc="someValue">Some content</MyComponent>`)).toThrow(
"Cannot have t-esc on a component that already has content"
+1 -61
View File
@@ -105,64 +105,6 @@ describe("t-foreach", () => {
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", () => {
const template = `
<div>
@@ -251,9 +193,7 @@ describe("t-foreach", () => {
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>`;
expect(() => renderToString(test)).toThrow(
'Invalid loop expression: "undefined" is not iterable'
);
expect(() => renderToString(test)).toThrow("Invalid loop expression");
});
test("t-foreach with t-if inside", () => {