mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
committed by
Géry Debongnie
parent
bced9f3d04
commit
26f14180e2
+20
-2
@@ -294,14 +294,14 @@ There is another way to format a string attribute: the `t-attf-` directive. With
|
|||||||
it, you get string interpolation:
|
it, you get string interpolation:
|
||||||
|
|
||||||
```xml
|
```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> -->
|
<!-- 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:
|
For historical reason, there is an alternate form of string interpolation:
|
||||||
|
|
||||||
```xml
|
```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> -->
|
<!-- 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`
|
Note: if used on a component, the reference will be set in the `refs`
|
||||||
variable between `willPatch` and `patched`.
|
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
|
### `t-key` directive
|
||||||
|
|
||||||
Even though Owl tries to be as declarative as possible, some DOM state is still
|
Even though Owl tries to be as declarative as possible, some DOM state is still
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</label>
|
</label>
|
||||||
<button class="destroy" t-on-click="removeTodo"></button>
|
<button class="destroy" t-on-click="removeTodo"></button>
|
||||||
</div>
|
</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>
|
</li>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
<div t-name="TabbedEditor" class="tabbed-editor">
|
<div t-name="TabbedEditor" class="tabbed-editor">
|
||||||
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
||||||
<t t-foreach="['js', 'xml', 'css']" t-as="tab">
|
<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"/>
|
<t t-esc="tab"/>
|
||||||
</a>
|
</a>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="code-editor" t-ref="'editor'"></div>
|
<div class="code-editor" t-ref="editor"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="App" class="playground">
|
<div t-name="App" class="playground">
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<div t-if="state.error" class="error">
|
<div t-if="state.error" class="error">
|
||||||
<t t-esc="state.error"/>
|
<t t-esc="state.error"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" t-ref="'content'"/>
|
<div class="content" t-ref="content"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</templates>
|
</templates>
|
||||||
+27
-10
@@ -483,9 +483,6 @@ export class QWeb {
|
|||||||
props.push(`${key}: _${val}`);
|
props.push(`${key}: _${val}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function formatter(expr) {
|
|
||||||
return "${" + ctx.formatExpression(expr) + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
let name = attributes[i].name;
|
let name = attributes[i].name;
|
||||||
@@ -544,17 +541,13 @@ export class QWeb {
|
|||||||
// attribute contains 'non letters' => we want to quote it
|
// attribute contains 'non letters' => we want to quote it
|
||||||
attName = '"' + attName + '"';
|
attName = '"' + attName + '"';
|
||||||
}
|
}
|
||||||
const formattedExpr = value!
|
const formattedExpr = ctx.interpolate(value);
|
||||||
.replace(/\{\{.*?\}\}/g, s => formatter(s.slice(2, -2)))
|
|
||||||
.replace(/\#\{.*?\}/g, s => formatter(s.slice(2, -1)));
|
|
||||||
const attID = ctx.generateID();
|
const attID = ctx.generateID();
|
||||||
let staticVal = (<Element>node).getAttribute(attName);
|
let staticVal = (<Element>node).getAttribute(attName);
|
||||||
if (staticVal) {
|
if (staticVal) {
|
||||||
ctx.addLine(
|
ctx.addLine(`var _${attID} = '${staticVal} ' + ${formattedExpr};`);
|
||||||
`var _${attID} = '${staticVal} ' + \`${formattedExpr}\`;`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
ctx.addLine(`var _${attID} = \`${formattedExpr}\`;`);
|
ctx.addLine(`var _${attID} = ${formattedExpr};`);
|
||||||
}
|
}
|
||||||
attrs.push(`${attName}: _${attID}`);
|
attrs.push(`${attName}: _${attID}`);
|
||||||
}
|
}
|
||||||
@@ -758,4 +751,28 @@ export class Context {
|
|||||||
const result = r.slice(0, -1);
|
const result = r.slice(0, -1);
|
||||||
return result;
|
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 + "`";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ QWeb.addDirective({
|
|||||||
priority: 95,
|
priority: 95,
|
||||||
atNodeCreation({ ctx, value, addNodeHook }) {
|
atNodeCreation({ ctx, value, addNodeHook }) {
|
||||||
const refKey = `ref${ctx.generateID()}`;
|
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;`);
|
addNodeHook("create", `context.refs[${refKey}] = n.elm;`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -391,7 +391,7 @@ QWeb.addDirective({
|
|||||||
let refKey: string = "";
|
let refKey: string = "";
|
||||||
if (ref) {
|
if (ref) {
|
||||||
refKey = `ref${ctx.generateID()}`;
|
refKey = `ref${ctx.generateID()}`;
|
||||||
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(ref)}`);
|
ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`);
|
||||||
refExpr = `context.refs[${refKey}] = w${widgetID};`;
|
refExpr = `context.refs[${refKey}] = w${widgetID};`;
|
||||||
}
|
}
|
||||||
let transitionsInsertCode = "";
|
let transitionsInsertCode = "";
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ exports[`attributes format expression 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`\${context['value'] + 37}\`;
|
var _1 = context['value'] + 37;
|
||||||
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -70,7 +70,7 @@ exports[`attributes format expression, other format 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`\${context['value'] + 37}\`;
|
var _1 = context['value'] + 37;
|
||||||
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -1494,7 +1494,7 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
|
|||||||
let c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
const ref3 = 'myspan' + 3
|
const ref3 = \`myspan\${context['id']}\`;
|
||||||
p2.hook = {
|
p2.hook = {
|
||||||
create: (_, n) => {
|
create: (_, n) => {
|
||||||
context.refs[ref3] = n.elm;
|
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};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
const ref3 = 'myspan'
|
const ref3 = \`myspan\`;
|
||||||
p2.hook = {
|
p2.hook = {
|
||||||
create: (_, n) => {
|
create: (_, n) => {
|
||||||
context.refs[ref3] = n.elm;
|
context.refs[ref3] = n.elm;
|
||||||
@@ -1546,7 +1546,7 @@ exports[`t-ref refs in a loop 1`] = `
|
|||||||
let c5 = [], p5 = {key:context['item']};
|
let c5 = [], p5 = {key:context['item']};
|
||||||
var vn5 = h('div', p5, c5);
|
var vn5 = h('div', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
const ref6 = context['item']
|
const ref6 = context['item'];
|
||||||
p5.hook = {
|
p5.hook = {
|
||||||
create: (_, n) => {
|
create: (_, n) => {
|
||||||
context.refs[ref6] = n.elm;
|
context.refs[ref6] = n.elm;
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ describe("animations", () => {
|
|||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"TestWidget",
|
"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 {
|
class TestWidget extends Widget {
|
||||||
state = { hide: false };
|
state = { hide: false };
|
||||||
|
|||||||
@@ -896,7 +896,7 @@ describe("composition", () => {
|
|||||||
test("t-refs on widget are widgets", async () => {
|
test("t-refs on widget are widgets", async () => {
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"WidgetC",
|
"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 {
|
class WidgetC extends Widget {
|
||||||
widgets = { b: WidgetB };
|
widgets = { b: WidgetB };
|
||||||
@@ -912,7 +912,7 @@ describe("composition", () => {
|
|||||||
"ParentWidget",
|
"ParentWidget",
|
||||||
`
|
`
|
||||||
<div>
|
<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>`
|
</div>`
|
||||||
);
|
);
|
||||||
class ParentWidget extends Widget {
|
class ParentWidget extends Widget {
|
||||||
@@ -938,8 +938,8 @@ describe("composition", () => {
|
|||||||
"ParentWidget",
|
"ParentWidget",
|
||||||
`
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-if="state.child1" t-ref="'child1'" 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"/>
|
<t t-if="state.child2" t-ref="child2" t-widget="Widget"/>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
class ParentWidget extends Widget {
|
class ParentWidget extends Widget {
|
||||||
@@ -1004,7 +1004,7 @@ describe("composition", () => {
|
|||||||
"ParentWidget",
|
"ParentWidget",
|
||||||
`<div>
|
`<div>
|
||||||
<t t-foreach="state.items" t-as="item">
|
<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>
|
</t>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
@@ -2277,7 +2277,7 @@ describe("t-mounted directive", () => {
|
|||||||
test("combined with a t-ref", async () => {
|
test("combined with a t-ref", async () => {
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"TestWidget",
|
"TestWidget",
|
||||||
`<div><input t-ref="'input'" t-mounted="f"/></div>`
|
`<div><input t-ref="input" t-mounted="f"/></div>`
|
||||||
);
|
);
|
||||||
class TestWidget extends Widget {
|
class TestWidget extends Widget {
|
||||||
f() {}
|
f() {}
|
||||||
|
|||||||
+5
-10
@@ -1,10 +1,5 @@
|
|||||||
import { QWeb } from "../src/qweb_core";
|
import { QWeb } from "../src/qweb_core";
|
||||||
import {
|
import { normalize, renderToDOM, renderToString, trim } from "./helpers";
|
||||||
normalize,
|
|
||||||
renderToDOM,
|
|
||||||
renderToString,
|
|
||||||
trim
|
|
||||||
} from "./helpers";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Setup and helpers
|
// Setup and helpers
|
||||||
@@ -1056,16 +1051,16 @@ describe("t-on", () => {
|
|||||||
|
|
||||||
describe("t-ref", () => {
|
describe("t-ref", () => {
|
||||||
test("can get a ref on a node", () => {
|
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 = {};
|
let refs: any = {};
|
||||||
renderToDOM(qweb, "test", { refs });
|
renderToDOM(qweb, "test", { refs });
|
||||||
expect(refs.myspan.tagName).toBe("SPAN");
|
expect(refs.myspan.tagName).toBe("SPAN");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can get a dynamic ref on a node", () => {
|
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 = {};
|
let refs: any = {};
|
||||||
renderToDOM(qweb, "test", { refs });
|
renderToDOM(qweb, "test", { refs, id: 3 });
|
||||||
expect(refs.myspan3.tagName).toBe("SPAN");
|
expect(refs.myspan3.tagName).toBe("SPAN");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1075,7 +1070,7 @@ describe("t-ref", () => {
|
|||||||
`
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="items" t-as="item">
|
<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>
|
</t>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -876,7 +876,7 @@ describe("connecting a component to store", () => {
|
|||||||
"Parent",
|
"Parent",
|
||||||
`
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-widget="Child" t-ref="'child'" t-props="{key: props.current}"/>
|
<t t-widget="Child" t-props="{key: props.current}"/>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user