diff --git a/README.md b/README.md
index ec8499c2..e7eceded 100644
--- a/README.md
+++ b/README.md
@@ -18,20 +18,16 @@ related projects. OWL's main features are:
Here is a short example to illustrate interactive components:
-```xml
-
-
-
`;
+
static components = { Counter };
+
}
-const qweb = new owl.QWeb(TEMPLATES);
-const app = new App({ qweb });
+const app = new App({ qweb: new QWeb() });
app.mount(document.body);
```
-Note that we assume here that the xml templates are available in the `TEMPLATES`
-string. More interesting examples can be found on the
+More interesting examples can be found on the
[playground](https://odoo.github.io/owl/playground) application.
## OWL's Design Principles
diff --git a/doc/component.md b/doc/component.md
index 1746b2dd..e916c396 100644
--- a/doc/component.md
+++ b/doc/component.md
@@ -72,7 +72,7 @@ Note that this code is written in ESNext style, so it will only run on the
latest browsers without a transpilation step.
This example show how a component should be defined: it simply subclasses the
-Component class. If no `template` key is defined, then
+Component class. If no static `template` key is defined, then
Owl will use the component's name as template name. Here,
a state object is defined. It is not mandatory to use the state object, but it
is certainly encouraged. The state object is [observed](observer.md), and any
@@ -96,9 +96,6 @@ find a template with the component name (or one of its ancestor).
- **`env`** (Object): the component environment, which contains a QWeb instance.
-- **`template`** (string, optional): if given, this is the name of the QWeb template that will render
- the component.
-
- **`state`** (Object): this is the location of the component's state, if there is
any. After the willStart method, the `state` property is observed, and each
change will cause the component to rerender itself.
@@ -113,7 +110,10 @@ find a template with the component name (or one of its ancestor).
### Static Properties
-- **`components`** (Object, optional): if given, this is an object that contains
+- **`template`** (string, optional): if given, this is the name of the QWeb template that will render the component. Note that there is a helper `xml` to
+ make it easy to define an inline template.
+
+* **`components`** (Object, optional): if given, this is an object that contains
the classes of any sub components needed by the template. This is the main way
used by Owl to be able to create sub components.
@@ -123,7 +123,7 @@ find a template with the component name (or one of its ancestor).
}
```
-- **`props`** (Object, optional): if given, this is an object that describes the
+* **`props`** (Object, optional): if given, this is an object that describes the
type and shape of the (actual) props given to the component. If Owl mode is
`dev`, this will be used to validate the props each time the component is
created/updated. See [Props Validation](#props-validation) for more information.
@@ -137,7 +137,7 @@ find a template with the component name (or one of its ancestor).
}
```
-* **`defaultProps`** (Object, optional): if given, this object define default
+- **`defaultProps`** (Object, optional): if given, this object define default
values for (top-level) props. Whenever `props` are given to the object, they
will be altered to add default value (if missing). Note that it does not
change the initial object, a new object will be created instead.
diff --git a/doc/quick_start.md b/doc/quick_start.md
index 8c497e4f..f4abc8ab 100644
--- a/doc/quick_start.md
+++ b/doc/quick_start.md
@@ -73,9 +73,9 @@ Let us now add the javascript to make it work, in `app.js`:
```javascript
class ClickCounter extends owl.Component {
+ static template = "clickcounter";
constructor() {
super(...arguments);
- this.template = "clickcounter";
this.state = { value: 0 };
}
diff --git a/doc/qweb.md b/doc/qweb.md
index c0e27025..e9c5c4b5 100644
--- a/doc/qweb.md
+++ b/doc/qweb.md
@@ -135,7 +135,7 @@ It's API is quite simple:
having a reference to the actual QWeb instance.
```js
- QWeb.registerTemplate('mytemplate', `
some template`);
+ QWeb.registerTemplate("mytemplate", `
some template`);
```
- **`registerComponent(name, Component)`**: static function to register an OWL Component
diff --git a/doc/readme.md b/doc/readme.md
index d3a3a4d3..9c5fd29c 100644
--- a/doc/readme.md
+++ b/doc/readme.md
@@ -19,6 +19,8 @@ owl
store
Store
ConnectedComponent
+ tags
+ xml
utils
debounce
escape
@@ -36,6 +38,7 @@ owl
- [QWeb](qweb.md)
- [Router](router.md)
- [Store](store.md)
+- [Tags](tags.md)
- [Utils](utils.md)
- [Virtual DOM](vdom.md)
diff --git a/doc/tags.md b/doc/tags.md
new file mode 100644
index 00000000..ecf32c3a
--- /dev/null
+++ b/doc/tags.md
@@ -0,0 +1,47 @@
+# 🦉 Tags 🦉
+
+Tags are very small helper to make it easy to write inline templates. There is
+only one currently available tag: `xml`, but we plan to add other tags later,
+such as a `css` tag, which will be used to write single file components.
+
+## XML tag
+
+Without tags, creating a standalone component would look like this:
+
+```js
+import { Component } from 'owl'
+
+const name = 'some-unique-name';
+const template = `
+
+ text
+
+
+`;
+QWeb.registerTemplate(name, template);
+
+class MyComponent extends Component {
+ static template = name;
+
+ ...
+}
+```
+
+With tags, this process is slightly simplified. The name is uniquely generated,
+and the template is automatically registered:
+
+```js
+import { Component } from 'owl'
+import { xml } from 'owl/tags'
+
+class MyComponent extends Component {
+ static template = xml`
+
+ text
+
+
+ `;
+
+ ...
+}
+```
diff --git a/src/component/directive.ts b/src/component/directive.ts
index 43ffcc29..6115370f 100644
--- a/src/component/directive.ts
+++ b/src/component/directive.ts
@@ -419,7 +419,7 @@ QWeb.addDirective({
const clone = node.cloneNode(true);
const slotNodes = clone.querySelectorAll("[t-set]");
- const slotId = qweb.nextSlotId++;
+ const slotId = QWeb.nextSlotId++;
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
if (slotNodes.length) {
for (let i = 0, length = slotNodes.length; i < length; i++) {
@@ -428,7 +428,7 @@ QWeb.addDirective({
const key = slotNode.getAttribute("t-set")!;
slotNode.removeAttribute("t-set");
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
- qweb.slots[`${slotId}_${key}`] = slotFn;
+ QWeb.slots[`${slotId}_${key}`] = slotFn;
}
}
if (clone.childNodes.length) {
@@ -437,7 +437,7 @@ QWeb.addDirective({
t.appendChild(child);
}
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
- qweb.slots[`${slotId}_default`] = slotFn;
+ QWeb.slots[`${slotId}_default`] = slotFn;
}
}
diff --git a/src/index.ts b/src/index.ts
index 28907d81..01eccc00 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,7 @@ import { QWeb } from "./qweb/index";
import { ConnectedComponent } from "./store/connected_component";
import { Store } from "./store/store";
import * as _utils from "./utils";
+import * as _tags from "./tags";
import { Link } from "./router/Link";
import { RouteComponent } from "./router/RouteComponent";
import { Router } from "./router/Router";
@@ -20,7 +21,7 @@ export const core = { EventBus, Observer };
export const router = { Router, RouteComponent, Link };
export const store = { Store, ConnectedComponent };
export const utils = _utils;
-
+export const tags = _tags;
export const __info__ = {};
Object.defineProperty(__info__, "mode", {
diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts
index d3c083ef..e4f03491 100644
--- a/src/qweb/extensions.ts
+++ b/src/qweb/extensions.ts
@@ -227,7 +227,9 @@ QWeb.addDirective({
atNodeEncounter({ ctx, value }): boolean {
const slotKey = ctx.generateID();
ctx.rootContext.shouldDefineOwner = true;
- ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
+ ctx.addLine(
+ `const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];`
+ );
ctx.addIf(`slot${slotKey}`);
ctx.addLine(
`slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: extra.vars, parent: owner}));`
diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts
index 45c62e96..48a11528 100644
--- a/src/qweb/qweb.ts
+++ b/src/qweb/qweb.ts
@@ -143,15 +143,16 @@ export class QWeb extends EventBus {
static TEMPLATES: { [name: string]: Template } = {};
+ static nextId: number = 1;
+
h = h;
// dev mode enables better error messages or more costly validations
static dev: boolean = false;
-
// slots contains sub templates defined with t-set inside t-component nodes, and
// are meant to be used by the t-slot directive.
- slots = {};
- nextSlotId = 1;
+ static slots = {};
+ static nextSlotId = 1;
// recursiveTemplates contains sub templates called with t-call, but which
// ends up in recursive situations. This is very similar to the slot situation,
diff --git a/src/router/Link.ts b/src/router/Link.ts
index dcb9af30..c97c9d8d 100644
--- a/src/router/Link.ts
+++ b/src/router/Link.ts
@@ -1,18 +1,18 @@
import { Component } from "../component/component";
+import { xml } from "../tags";
import { Destination, RouterEnv } from "./Router";
-export const LINK_TEMPLATE_NAME = "__owl__-router-link";
-export const LINK_TEMPLATE = `
-
-
- `;
-
type Props = Destination;
export class Link extends Component {
- static template = LINK_TEMPLATE_NAME;
+ static template = xml`
+
+
+
+ `;
+
href: string = this.env.router.destToPath(this.props);
async willUpdateProps(nextProps) {
diff --git a/src/router/RouteComponent.ts b/src/router/RouteComponent.ts
index 162d047a..faef639c 100644
--- a/src/router/RouteComponent.ts
+++ b/src/router/RouteComponent.ts
@@ -1,15 +1,15 @@
import { Component } from "../component/component";
+import { xml } from "../tags";
-export const ROUTE_COMPONENT_TEMPLATE_NAME = "__owl__-router-component";
-export const ROUTE_COMPONENT_TEMPLATE = `
+export class RouteComponent extends Component {
+ static template = xml`
- `;
+
+ `;
-export class RouteComponent extends Component {
- static template = ROUTE_COMPONENT_TEMPLATE_NAME;
routes: any[] = [];
constructor(parent, props) {
super(parent, props);
diff --git a/src/router/Router.ts b/src/router/Router.ts
index d039c4f1..7eedb6a1 100644
--- a/src/router/Router.ts
+++ b/src/router/Router.ts
@@ -1,7 +1,5 @@
import { Env } from "../component/component";
import { QWeb } from "../qweb/index";
-import { ROUTE_COMPONENT_TEMPLATE, ROUTE_COMPONENT_TEMPLATE_NAME } from "./RouteComponent";
-import { LINK_TEMPLATE, LINK_TEMPLATE_NAME } from "./Link";
import { shallowEqual } from "../utils";
type NavigationGuard = (info: {
@@ -84,10 +82,6 @@ export class Router {
this.routes[partialRoute.name] = partialRoute as Route;
this.routeIds.push(partialRoute.name);
}
-
- // setup link and directive
- env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE);
- env.qweb.addTemplate(ROUTE_COMPONENT_TEMPLATE_NAME, ROUTE_COMPONENT_TEMPLATE);
}
//--------------------------------------------------------------------------
diff --git a/src/tags.ts b/src/tags.ts
new file mode 100644
index 00000000..3e5d2352
--- /dev/null
+++ b/src/tags.ts
@@ -0,0 +1,28 @@
+import { QWeb } from "./qweb/index";
+
+/**
+ * Owl Tags
+ *
+ * We have here a (very) small collection of tag functions:
+ *
+ * - xml
+ *
+ * The plan is to add a few other tags such as css, globalcss.
+ */
+
+
+
+/**
+ * XML tag helper for defining templates. With this, one can simply define
+ * an inline template with just the template xml:
+ * ```js
+ * class A extends Component {
+ * static template = xml`
`);
@@ -164,19 +165,17 @@ describe("basic widget properties", () => {
test("reconciliation alg is not confused in some specific situation", async () => {
// in this test, we set t-key to 4 because it was in conflict with the
// template id corresponding to the first child.
- env.qweb.addTemplates(`
-
-
+ `;
static components = { child: ChildWidget };
state = { n: 1 };
willPatch() {
@@ -763,13 +755,6 @@ describe("lifecycle hooks", () => {
// we make sure here that willPatch/patched is only called if widget is in
// dom, mounted
const steps: string[] = [];
- env.qweb.addTemplates(`
-
-