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 ,