[IMP] qweb: t-ref: dyn values with string interpolation

Closes #100
This commit is contained in:
Aaron Bohy
2019-05-28 13:17:38 +02:00
parent bced9f3d04
commit 53e53e9bad
10 changed files with 71 additions and 41 deletions
+20 -2
View File
@@ -294,14 +294,14 @@ There is another way to format a string attribute: the `t-attf-` directive. With
it, you get string interpolation:
```xml
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
<div t-attf-foo="a #{value1} is #{value2} of #{value3} ]"/>
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
```
For historical reason, there is an alternate form of string interpolation:
```xml
<div t-attf-foo="a #{value1} is #{value2} of #{value3} ]"/>
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
```
@@ -480,6 +480,24 @@ some method on a sub widget.
Note: if used on a component, the reference will be set in the `refs`
variable between `willPatch` and `patched`.
The `t-ref` directive also accepts dynamic values with string interpolation
(like the `t-attf-` directive). For example, if we have `id` set to 44 in the
rendering context,
```xml
<div t-ref="widget_#{id}"/>
```
```js
this.refs.widget_44;
```
Similarly to `t-attf-`, there is an alternate form of string interpolation:
```xml
<div t-ref="widget_{{id}}"/>
```
### `t-key` directive
Even though Owl tries to be as declarative as possible, some DOM state is still
+1 -1
View File
@@ -551,7 +551,7 @@ const TODO_APP_STORE_XML = `<templates>
</label>
<button class="destroy" t-on-click="removeTodo"></button>
</div>
<input class="edit" t-ref="'input'" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-mounted="focusInput" t-on-blur="handleBlur"/>
<input class="edit" t-ref="input" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-mounted="focusInput" t-on-blur="handleBlur"/>
</li>
</templates>`;
+3 -3
View File
@@ -2,12 +2,12 @@
<div t-name="TabbedEditor" class="tabbed-editor">
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
<t t-foreach="['js', 'xml', 'css']" t-as="tab">
<a t-ref="tab" t-if="props[tab]" t-key="tab" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
<a t-ref="{{tab}}" t-if="props[tab]" t-key="tab" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
<t t-esc="tab"/>
</a>
</t>
</div>
<div class="code-editor" t-ref="'editor'"></div>
<div class="code-editor" t-ref="editor"></div>
</div>
<div t-name="App" class="playground">
@@ -48,7 +48,7 @@
<div t-if="state.error" class="error">
<t t-esc="state.error"/>
</div>
<div class="content" t-ref="'content'"/>
<div class="content" t-ref="content"/>
</div>
</div>
</templates>
+27 -10
View File
@@ -483,9 +483,6 @@ export class QWeb {
props.push(`${key}: _${val}`);
}
}
function formatter(expr) {
return "${" + ctx.formatExpression(expr) + "}";
}
for (let i = 0; i < attributes.length; i++) {
let name = attributes[i].name;
@@ -544,17 +541,13 @@ export class QWeb {
// attribute contains 'non letters' => we want to quote it
attName = '"' + attName + '"';
}
const formattedExpr = value!
.replace(/\{\{.*?\}\}/g, s => formatter(s.slice(2, -2)))
.replace(/\#\{.*?\}/g, s => formatter(s.slice(2, -1)));
const formattedExpr = ctx.interpolate(value);
const attID = ctx.generateID();
let staticVal = (<Element>node).getAttribute(attName);
if (staticVal) {
ctx.addLine(
`var _${attID} = '${staticVal} ' + \`${formattedExpr}\`;`
);
ctx.addLine(`var _${attID} = '${staticVal} ' + ${formattedExpr};`);
} else {
ctx.addLine(`var _${attID} = \`${formattedExpr}\`;`);
ctx.addLine(`var _${attID} = ${formattedExpr};`);
}
attrs.push(`${attName}: _${attID}`);
}
@@ -758,4 +751,28 @@ export class Context {
const result = r.slice(0, -1);
return result;
}
/**
* Perform string interpolation on the given string. Note that if the whole
* string is an expression, it simply returns it (formatted).
* For instance:
* 'Hello {{x}}!' -> `Hello ${x}`
* '{{x}}' -> x
*/
interpolate(s: string): string {
let matches = s.match(/\{\{.*?\}\}/g);
if (matches && matches[0].length === s.length) {
return this.formatExpression(s.slice(2, -2));
}
matches = s.match(/\#\{.*?\}/g);
if (matches && matches[0].length === s.length) {
return this.formatExpression(s.slice(2, -1));
}
let formatter = expr => "${" + this.formatExpression(expr) + "}";
let r = s
.replace(/\{\{.*?\}\}/g, s => formatter(s.slice(2, -2)))
.replace(/\#\{.*?\}/g, s => formatter(s.slice(2, -1)));
return "`" + r + "`";
}
}
+2 -2
View File
@@ -82,7 +82,7 @@ QWeb.addDirective({
priority: 95,
atNodeCreation({ ctx, value, addNodeHook }) {
const refKey = `ref${ctx.generateID()}`;
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`);
ctx.addLine(`const ${refKey} = ${ctx.interpolate(value)};`);
addNodeHook("create", `context.refs[${refKey}] = n.elm;`);
}
});
@@ -391,7 +391,7 @@ QWeb.addDirective({
let refKey: string = "";
if (ref) {
refKey = `ref${ctx.generateID()}`;
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(ref)}`);
ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`);
refExpr = `context.refs[${refKey}] = w${widgetID};`;
}
let transitionsInsertCode = "";
+5 -5
View File
@@ -59,7 +59,7 @@ exports[`attributes format expression 1`] = `
"function anonymous(context,extra
) {
var h = this.utils.h;
var _1 = \`\${context['value'] + 37}\`;
var _1 = context['value'] + 37;
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
var vn2 = h('div', p2, c2);
return vn2;
@@ -70,7 +70,7 @@ exports[`attributes format expression, other format 1`] = `
"function anonymous(context,extra
) {
var h = this.utils.h;
var _1 = \`\${context['value'] + 37}\`;
var _1 = context['value'] + 37;
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
var vn2 = h('div', p2, c2);
return vn2;
@@ -1494,7 +1494,7 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
const ref3 = 'myspan' + 3
const ref3 = \`myspan\${context['id']}\`;
p2.hook = {
create: (_, n) => {
context.refs[ref3] = n.elm;
@@ -1513,7 +1513,7 @@ exports[`t-ref can get a ref on a node 1`] = `
let c2 = [], p2 = {key:2};
var vn2 = h('span', p2, c2);
c1.push(vn2);
const ref3 = 'myspan'
const ref3 = \`myspan\`;
p2.hook = {
create: (_, n) => {
context.refs[ref3] = n.elm;
@@ -1546,7 +1546,7 @@ exports[`t-ref refs in a loop 1`] = `
let c5 = [], p5 = {key:context['item']};
var vn5 = h('div', p5, c5);
c1.push(vn5);
const ref6 = context['item']
const ref6 = context['item'];
p5.hook = {
create: (_, n) => {
context.refs[ref6] = n.elm;
+1 -1
View File
@@ -145,7 +145,7 @@ describe("animations", () => {
env.qweb.addTemplate(
"TestWidget",
`<div><span t-ref="'span'" t-transition="chimay">blue</span></div>`
`<div><span t-ref="span" t-transition="chimay">blue</span></div>`
);
class TestWidget extends Widget {
state = { hide: false };
+6 -6
View File
@@ -896,7 +896,7 @@ describe("composition", () => {
test("t-refs on widget are widgets", async () => {
env.qweb.addTemplate(
"WidgetC",
`<div>Hello<t t-ref="'mywidgetb'" t-widget="b"/></div>`
`<div>Hello<t t-ref="mywidgetb" t-widget="b"/></div>`
);
class WidgetC extends Widget {
widgets = { b: WidgetB };
@@ -912,7 +912,7 @@ describe("composition", () => {
"ParentWidget",
`
<div>
<t t-foreach="state.list" t-as="elem" t-ref="'child'" t-key="elem" t-widget="Widget"/>
<t t-foreach="state.list" t-as="elem" t-ref="child" t-key="elem" t-widget="Widget"/>
</div>`
);
class ParentWidget extends Widget {
@@ -938,8 +938,8 @@ describe("composition", () => {
"ParentWidget",
`
<div>
<t t-if="state.child1" t-ref="'child1'" t-widget="Widget"/>
<t t-if="state.child2" t-ref="'child2'" t-widget="Widget"/>
<t t-if="state.child1" t-ref="child1" t-widget="Widget"/>
<t t-if="state.child2" t-ref="child2" t-widget="Widget"/>
</div>`
);
class ParentWidget extends Widget {
@@ -1004,7 +1004,7 @@ describe("composition", () => {
"ParentWidget",
`<div>
<t t-foreach="state.items" t-as="item">
<t t-widget="Child" t-ref="item" t-key="item"/>
<t t-widget="Child" t-ref="{{item}}" t-key="item"/>
</t>
</div>`
);
@@ -2277,7 +2277,7 @@ describe("t-mounted directive", () => {
test("combined with a t-ref", async () => {
env.qweb.addTemplate(
"TestWidget",
`<div><input t-ref="'input'" t-mounted="f"/></div>`
`<div><input t-ref="input" t-mounted="f"/></div>`
);
class TestWidget extends Widget {
f() {}
+5 -10
View File
@@ -1,10 +1,5 @@
import { QWeb } from "../src/qweb_core";
import {
normalize,
renderToDOM,
renderToString,
trim
} from "./helpers";
import { normalize, renderToDOM, renderToString, trim } from "./helpers";
//------------------------------------------------------------------------------
// Setup and helpers
@@ -1056,16 +1051,16 @@ describe("t-on", () => {
describe("t-ref", () => {
test("can get a ref on a node", () => {
qweb.addTemplate("test", `<div><span t-ref="'myspan'"/></div>`);
qweb.addTemplate("test", `<div><span t-ref="myspan"/></div>`);
let refs: any = {};
renderToDOM(qweb, "test", { refs });
expect(refs.myspan.tagName).toBe("SPAN");
});
test("can get a dynamic ref on a node", () => {
qweb.addTemplate("test", `<div><span t-ref="'myspan' + 3"/></div>`);
qweb.addTemplate("test", `<div><span t-ref="myspan{{id}}"/></div>`);
let refs: any = {};
renderToDOM(qweb, "test", { refs });
renderToDOM(qweb, "test", { refs, id: 3 });
expect(refs.myspan3.tagName).toBe("SPAN");
});
@@ -1075,7 +1070,7 @@ describe("t-ref", () => {
`
<div>
<t t-foreach="items" t-as="item">
<div t-ref="item" t-key="item"><t t-esc="item"/></div>
<div t-ref="{{item}}" t-key="item"><t t-esc="item"/></div>
</t>
</div>`
);
+1 -1
View File
@@ -876,7 +876,7 @@ describe("connecting a component to store", () => {
"Parent",
`
<div>
<t t-widget="Child" t-ref="'child'" t-props="{key: props.current}"/>
<t t-widget="Child" t-props="{key: props.current}"/>
</div>
`
);