From 233c953243cc8ef9cc55391714b084ad4f0b07f6 Mon Sep 17 00:00:00 2001 From: Bruno Boi Date: Tue, 2 Nov 2021 11:29:23 +0100 Subject: [PATCH] [IMP] qweb: introduce t-model directive supported modifiers: lazy, trim, number --- src/blockdom/attributes.ts | 6 +- src/qweb/compiler.ts | 37 +- src/qweb/parser.ts | 71 +++- src/qweb/template_helpers.ts | 6 + .../components/__snapshots__/app.test.ts.snap | 2 +- .../__snapshots__/basics.test.ts.snap | 158 ++++---- .../__snapshots__/concurrency.test.ts.snap | 152 ++++---- .../__snapshots__/error_handling.test.ts.snap | 4 +- .../__snapshots__/event_handling.test.ts.snap | 8 +- .../higher_order_component.test.ts.snap | 26 +- .../__snapshots__/hooks.test.ts.snap | 16 +- .../__snapshots__/lifecycle.test.ts.snap | 108 +++--- .../__snapshots__/props.test.ts.snap | 20 +- .../props_validation.test.ts.snap | 62 ++-- .../__snapshots__/reactivity.test.ts.snap | 10 +- .../__snapshots__/refs.test.ts.snap | 4 +- .../__snapshots__/slots.test.ts.snap | 234 ++++++------ .../__snapshots__/style_class.test.ts.snap | 64 ++-- .../__snapshots__/t_call.test.ts.snap | 38 +- .../__snapshots__/t_call_block.test.ts.snap | 2 +- .../__snapshots__/t_component.test.ts.snap | 34 +- .../__snapshots__/t_foreach.test.ts.snap | 32 +- .../__snapshots__/t_model.test.ts.snap | 346 ++++++++++++++++++ .../__snapshots__/t_on.test.ts.snap | 12 +- .../__snapshots__/t_props.test.ts.snap | 8 +- .../__snapshots__/t_set.test.ts.snap | 16 +- tests/components/t_model.test.ts | 117 +++++- tests/misc/__snapshots__/memo.test.ts.snap | 10 +- tests/misc/__snapshots__/portal.test.ts.snap | 48 +-- .../__snapshots__/attributes.test.ts.snap | 90 ++--- .../qweb/__snapshots__/comments.test.ts.snap | 6 +- .../__snapshots__/error_handling.test.ts.snap | 2 +- .../__snapshots__/event_handling.test.ts.snap | 66 ++-- tests/qweb/__snapshots__/misc.test.ts.snap | 12 +- .../__snapshots__/qweb_memory.test.ts.snap | 2 +- .../simple_templates.test.ts.snap | 54 +-- tests/qweb/__snapshots__/t_call.test.ts.snap | 112 +++--- .../__snapshots__/t_debug_log.test.ts.snap | 8 +- tests/qweb/__snapshots__/t_esc.test.ts.snap | 24 +- .../qweb/__snapshots__/t_foreach.test.ts.snap | 36 +- tests/qweb/__snapshots__/t_if.test.ts.snap | 50 +-- tests/qweb/__snapshots__/t_key.test.ts.snap | 4 +- tests/qweb/__snapshots__/t_raw.test.ts.snap | 24 +- tests/qweb/__snapshots__/t_ref.test.ts.snap | 14 +- tests/qweb/__snapshots__/t_set.test.ts.snap | 56 +-- .../__snapshots__/translation.test.ts.snap | 10 +- .../__snapshots__/white_space.test.ts.snap | 12 +- tests/qweb/parser.test.ts | 207 ++++++++++- 48 files changed, 1589 insertions(+), 851 deletions(-) create mode 100644 tests/components/__snapshots__/t_model.test.ts.snap diff --git a/src/blockdom/attributes.ts b/src/blockdom/attributes.ts index 5217a295..6ca49ed7 100644 --- a/src/blockdom/attributes.ts +++ b/src/blockdom/attributes.ts @@ -144,10 +144,10 @@ export function isProp(tag: string, key: string): boolean { case "option": return key === "selected" || key === "disabled"; case "textarea": - return key === "readonly" || key === "disabled"; - break; - case "button": + return key === "value" || key === "readonly" || key === "disabled"; case "select": + return key === "value" || key === "disabled"; + case "button": case "optgroup": return key === "disabled"; } diff --git a/src/qweb/compiler.ts b/src/qweb/compiler.ts index d3aaa539..b445bae7 100644 --- a/src/qweb/compiler.ts +++ b/src/qweb/compiler.ts @@ -277,7 +277,7 @@ export class QWebCompiler { // define blocks and utility functions this.addLine(`let { text, createBlock, list, multi, html, toggler, component } = bdom;`); this.addLine( - `let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;` + `let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;` ); if (this.shouldDefineAssign) { this.addLine(`let assign = Object.assign;`); @@ -554,6 +554,41 @@ export class QWebCompiler { } } + // t-model + if (ast.model) { + const { + baseExpr, + expr, + eventType, + shouldNumberize, + shouldTrim, + targetAttr, + specialInitTargetAttr, + } = ast.model; + + const baseExpression = compileExpr(baseExpr); + const id = this.generateId(); + this.addLine(`const bExpr${id} = ${baseExpression};`); + + const expression = compileExpr(expr); + + let idx: number; + if (specialInitTargetAttr) { + idx = block!.insertData(`${baseExpression}[${expression}] === '${attrs[targetAttr]}'`); + attrs[`block-attribute-${idx}`] = specialInitTargetAttr; + } else { + idx = block!.insertData(`${baseExpression}[${expression}]`); + attrs[`block-attribute-${idx}`] = targetAttr; + } + let valueCode = `ev.target.${targetAttr}`; + valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode; + valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode; + + const handler = `[(ev) => { bExpr${id}[${expression}] = ${valueCode}; }]`; + idx = block!.insertData(handler); + attrs[`block-handler-${idx}`] = eventType; + } + const dom = xmlDoc.createElement(ast.tag); for (const [attr, val] of Object.entries(attrs)) { if (!(attr === "class" && val === "")) { diff --git a/src/qweb/parser.ts b/src/qweb/parser.ts index 8d0ae306..34fb80c4 100644 --- a/src/qweb/parser.ts +++ b/src/qweb/parser.ts @@ -39,6 +39,15 @@ export interface ASTDomNode { content: AST[]; ref: string | null; on: { [key: string]: string }; + model: { + baseExpr: string; + expr: string; + targetAttr: string; + specialInitTargetAttr: string | null; + eventType: "change" | "click" | "input"; + shouldTrim: boolean; + shouldNumberize: boolean; + } | null; } export interface ASTMulti { @@ -275,13 +284,16 @@ function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null { // ----------------------------------------------------------------------------- // Regular dom node // ----------------------------------------------------------------------------- +const hasDotAtTheEnd = /\.[\w_]+\s*$/; +const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/; function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { - if (node.tagName === "t") { + const { tagName } = node; + if (tagName === "t") { return null; } const children: AST[] = []; - if (node.tagName === "pre") { + if (tagName === "pre") { ctx = { inPreTag: true }; } let ref = null; @@ -297,16 +309,64 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { } } + const nodeAttrsNames = node.getAttributeNames(); const attrs: ASTDomNode["attrs"] = {}; const on: ASTDomNode["on"] = {}; + let model: ASTDomNode["model"] = null; - for (let attr of node.getAttributeNames()) { + for (let attr of nodeAttrsNames) { const value = node.getAttribute(attr)!; if (attr.startsWith("t-on")) { if (attr === "t-on") { throw new Error("Missing event name with t-on directive"); } on[attr.slice(5)] = value; + } else if (attr.startsWith("t-model")) { + if (!["input", "select", "textarea"].includes(tagName)) { + throw new Error("The t-model directive only works with ,