diff --git a/src/component.ts b/src/component.ts index bce24264..736e64f1 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,5 +1,6 @@ import h from "../libs/snabbdom/src/h"; import sdAttrs from "../libs/snabbdom/src/modules/attributes"; +import sdProps from "../libs/snabbdom/src/modules/props"; import sdListeners from "../libs/snabbdom/src/modules/eventlisteners"; import { init } from "../libs/snabbdom/src/snabbdom"; import { VNode } from "../libs/snabbdom/src/vnode"; @@ -35,7 +36,7 @@ export interface Meta { boundHandlers: { [key: number]: any }; } -const patch = init([sdListeners, sdAttrs]); +const patch = init([sdListeners, sdAttrs, sdProps]); export interface Type extends Function { new (...args: any[]): T; diff --git a/src/qweb.ts b/src/qweb.ts index c9303275..9a5c5cf3 100644 --- a/src/qweb.ts +++ b/src/qweb.ts @@ -23,6 +23,15 @@ const WORD_REPLACEMENT = { lte: "<=" }; +const DISABLED_TAGS = [ + "input", + "textarea", + "button", + "select", + "option", + "optgroup" +]; + //------------------------------------------------------------------------------ // Compilation Context //------------------------------------------------------------------------------ @@ -413,7 +422,34 @@ export class QWeb { } const attributes = (node).attributes; const attrs: string[] = []; + const props: string[] = []; const tattrs: number[] = []; + + function handleBooleanProps(key, val) { + let isProp = false; + if (node.nodeName === "input" && key === "checked") { + let type = (node).getAttribute("type"); + if (type === "checkbox" || type === "radio") { + isProp = true; + } + } + if (node.nodeName === "option" && key === "selected") { + isProp = true; + } + if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) { + isProp = true; + } + if ( + (key === "readonly" && node.nodeName === "input") || + node.nodeName === "textarea" + ) { + isProp = true; + } + if (isProp) { + props.push(`${key}: _${val}`); + } + } + for (let i = 0; i < attributes.length; i++) { let name = attributes[i].name; const value = attributes[i].textContent!; @@ -427,6 +463,7 @@ export class QWeb { name = '"' + name + '"'; } attrs.push(`${name}: _${attID}`); + handleBooleanProps(name, attID); } // dynamic attributes @@ -448,6 +485,7 @@ export class QWeb { } ctx.addLine(`let _${attID} = ${formattedValue};`); attrs.push(`${attName}: _${attID}`); + handleBooleanProps(attName, attID); } if (name.startsWith("t-attf-")) { @@ -477,6 +515,9 @@ export class QWeb { if (attrs.length + tattrs.length > 0) { parts.push(`attrs:{${attrs.join(",")}}`); } + if (props.length > 0) { + parts.push(`props:{${props.join(",")}}`); + } if (withHandlers) { parts.push(`on:{}`); } diff --git a/tests/__snapshots__/qweb.test.ts.snap b/tests/__snapshots__/qweb.test.ts.snap index 3ed7abb0..a89cb29a 100644 --- a/tests/__snapshots__/qweb.test.ts.snap +++ b/tests/__snapshots__/qweb.test.ts.snap @@ -581,6 +581,74 @@ exports[`misc global 1`] = ` }" `; +exports[`special cases for some boolean html attributes/properties input type= checkbox, with t-att-checked 1`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let _1 = 'checkbox'; + let _2 = context['flag']; + let c3 = [], p3 = {key:3,attrs:{type: _1,checked: _2},props:{checked: _2}}; + let vn3 = h('input', p3, c3); + return vn3; +}" +`; + +exports[`special cases for some boolean html attributes/properties various boolean html attributes 1`] = ` +"function anonymous(context,extra +) { + let h = this.utils.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + c1.push({text: \` + \`}); + let _2 = 'checkbox'; + let _3 = 'checked'; + let c4 = [], p4 = {key:4,attrs:{type: _2,checked: _3},props:{checked: _3}}; + let vn4 = h('input', p4, c4); + c1.push(vn4); + c1.push({text: \` + \`}); + let _5 = 'checked'; + let c6 = [], p6 = {key:6,attrs:{checked: _5}}; + let vn6 = h('input', p6, c6); + c1.push(vn6); + c1.push({text: \` + \`}); + let _7 = 'checked'; + let c8 = [], p8 = {key:8,attrs:{checked: _7}}; + let vn8 = h('div', p8, c8); + c1.push(vn8); + c1.push({text: \` + \`}); + let _9 = 'selected'; + let c10 = [], p10 = {key:10,attrs:{selected: _9}}; + let vn10 = h('div', p10, c10); + c1.push(vn10); + c1.push({text: \` + \`}); + let _11 = 'selected'; + let _12 = '1'; + let c13 = [], p13 = {key:13,attrs:{selected: _11,other: _12},props:{selected: _11}}; + let vn13 = h('option', p13, c13); + c1.push(vn13); + c1.push({text: \` + \`}); + let _14 = 'readonly'; + let c15 = [], p15 = {key:15,attrs:{readonly: _14},props:{readonly: _14}}; + let vn15 = h('input', p15, c15); + c1.push(vn15); + c1.push({text: \` + \`}); + let _16 = 'disabled'; + let c17 = [], p17 = {key:17,attrs:{disabled: _16},props:{disabled: _16}}; + let vn17 = h('button', p17, c17); + c1.push(vn17); + c1.push({text: \` + \`}); + return vn1; +}" +`; + exports[`static templates div with a span child node 1`] = ` "function anonymous(context,extra ) { diff --git a/tests/qweb.test.ts b/tests/qweb.test.ts index 9c8aadbc..0461c496 100644 --- a/tests/qweb.test.ts +++ b/tests/qweb.test.ts @@ -886,3 +886,32 @@ describe("loading templates", () => { expect(qweb.processedTemplates).toEqual({}); }); }); + +describe("special cases for some boolean html attributes/properties", () => { + test("input type= checkbox, with t-att-checked", () => { + qweb.addTemplate("test", ``); + const result = renderToString(qweb, "test", { flag: true }); + expect(result).toBe(``); + }); + + test("various boolean html attributes", () => { + // the unique assertion here is the code snapshot automatically done by + // renderToString + expect.assertions(1); + qweb.addTemplate( + "test", + ` +
+ + +
+
+
+ ` + ); + renderToString(qweb, "test", { flag: true }); + }); +});