[IMP] owl: update to v0.14.0

This commit is contained in:
Géry Debongnie
2019-06-13 12:20:25 +02:00
parent 84f810e6ae
commit 44e2de9709
3 changed files with 299 additions and 129 deletions
+151 -102
View File
@@ -210,14 +210,7 @@
*/ */
function vnode(sel, data, children, text, elm) { function vnode(sel, data, children, text, elm) {
let key = data === undefined ? undefined : data.key; let key = data === undefined ? undefined : data.key;
return { return { sel, data, children, text, elm, key };
sel: sel,
data: data,
children: children,
text: text,
elm: elm,
key: key
};
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// snabbdom.ts // snabbdom.ts
@@ -885,7 +878,7 @@
"(": "LEFT_PAREN", "(": "LEFT_PAREN",
")": "RIGHT_PAREN" ")": "RIGHT_PAREN"
}; };
const OPERATORS = [".", "===", "==", "+", "!", "||", "&&", ">", "?", "-", "*"]; const OPERATORS = ".,===,==,+,!,||,&&,>=,>,<=,<,?,-,*,/,%".split(',');
let tokenizeString = function (expr) { let tokenizeString = function (expr) {
let s = expr[0]; let s = expr[0];
let start = s; let start = s;
@@ -997,7 +990,7 @@
} }
} }
if (expr.length) { if (expr.length) {
throw new Error("Tokenizer error..."); throw new Error(`Tokenizer error: could not tokenize "${expr}"`);
} }
return result; return result;
} }
@@ -1125,6 +1118,10 @@
// use case is that component's templates are qweb dependant, and need to be // use case is that component's templates are qweb dependant, and need to be
// able to map a qweb instance to a template name. // able to map a qweb instance to a template name.
this.id = nextID++; this.id = nextID++;
// slots contains sub templates defined with t-set inside t-widget nodes, and
// are meant to be used by the t-slot directive.
this.slots = {};
this.nextSlotId = 1;
if (data) { if (data) {
this.addTemplates(data); this.addTemplates(data);
} }
@@ -1288,13 +1285,16 @@
if (ctx.parentNode) { if (ctx.parentNode) {
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`); ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
} }
else if (ctx.parentTextNode) {
ctx.addLine(`vn${ctx.parentTextNode}.text += \`${text}\`;`);
}
else { else {
// this is an unusual situation: this text node is the result of the // this is an unusual situation: this text node is the result of the
// template rendering. // template rendering.
let nodeID = ctx.generateID(); let nodeID = ctx.generateID();
ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`); ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`);
ctx.rootContext.rootNode = nodeID; ctx.rootContext.rootNode = nodeID;
ctx.rootContext.parentNode = nodeID; ctx.rootContext.parentTextNode = nodeID;
} }
return; return;
} }
@@ -1543,6 +1543,7 @@
this.variables = {}; this.variables = {};
this.escaping = false; this.escaping = false;
this.parentNode = null; this.parentNode = null;
this.parentTextNode = null;
this.rootNode = null; this.rootNode = null;
this.indentLevel = 0; this.indentLevel = 0;
this.shouldDefineOwner = false; this.shouldDefineOwner = false;
@@ -1560,7 +1561,7 @@
return id; return id;
} }
withParent(node) { withParent(node) {
if (this === this.rootContext && this.parentNode) { if (this === this.rootContext && (this.parentNode || this.parentTextNode)) {
throw new Error("A template should not have more than one root node"); throw new Error("A template should not have more than one root node");
} }
if (!this.rootContext.rootNode) { if (!this.rootContext.rootNode) {
@@ -2268,11 +2269,19 @@
exprID = value.id; exprID = value.id;
} }
ctx.addIf(`${exprID} || ${exprID} === 0`); ctx.addIf(`${exprID} || ${exprID} === 0`);
if (!ctx.parentNode) {
throw new Error("Should not have a text node without a parent");
}
if (ctx.escaping) { if (ctx.escaping) {
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`); if (ctx.parentTextNode) {
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
}
else if (ctx.parentNode) {
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
}
else {
let nodeID = ctx.generateID();
ctx.rootContext.rootNode = nodeID;
ctx.rootContext.parentTextNode = nodeID;
ctx.addLine(`var vn${nodeID} = {text: ${exprID}};`);
}
} }
else { else {
let fragID = ctx.generateID(); let fragID = ctx.generateID();
@@ -2524,6 +2533,7 @@
* - t-transition * - t-transition
* - t-widget/t-keepalive * - t-widget/t-keepalive
* - t-mounted * - t-mounted
* - t-slot
*/ */
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// t-on // t-on
@@ -2595,7 +2605,15 @@
UTILS.nextFrame = function (cb) { UTILS.nextFrame = function (cb) {
requestAnimationFrame(() => requestAnimationFrame(cb)); requestAnimationFrame(() => requestAnimationFrame(cb));
}; };
UTILS.transitionInsert = function (elm, name) { UTILS.transitionInsert = function (vn, name) {
const elm = vn.elm;
// remove potential duplicated vnode that is currently being removed, to
// prevent from having twice the same node in the DOM during an animation
const dup = elm.parentElement &&
elm.parentElement.querySelector(`*[data-owl-key='${vn.key}']`);
if (dup) {
dup.remove();
}
elm.classList.add(name + "-enter"); elm.classList.add(name + "-enter");
elm.classList.add(name + "-enter-active"); elm.classList.add(name + "-enter-active");
const finalize = () => { const finalize = () => {
@@ -2608,7 +2626,9 @@
whenTransitionEnd(elm, finalize); whenTransitionEnd(elm, finalize);
}); });
}; };
UTILS.transitionRemove = function (elm, name, rm) { UTILS.transitionRemove = function (vn, name, rm) {
const elm = vn.elm;
elm.setAttribute("data-owl-key", vn.key);
elm.classList.add(name + "-leave"); elm.classList.add(name + "-leave");
elm.classList.add(name + "-leave-active"); elm.classList.add(name + "-leave-active");
const finalize = () => { const finalize = () => {
@@ -2656,8 +2676,8 @@
atNodeCreation({ value, addNodeHook }) { atNodeCreation({ value, addNodeHook }) {
let name = value; let name = value;
const hooks = { const hooks = {
insert: `this.utils.transitionInsert(vn.elm, '${name}');`, insert: `this.utils.transitionInsert(vn, '${name}');`,
remove: `this.utils.transitionRemove(vn.elm, '${name}', rm);` remove: `this.utils.transitionRemove(vn, '${name}', rm);`
}; };
for (let hookName in hooks) { for (let hookName in hooks) {
addNodeHook(hookName, hooks[hookName]); addNodeHook(hookName, hooks[hookName]);
@@ -2780,7 +2800,7 @@
* let nvn = w4._mount(vnode, vn.elm); * let nvn = w4._mount(vnode, vn.elm);
* pvnode.elm = nvn.elm; * pvnode.elm = nvn.elm;
* // what follows is only present if there are animations on the widget * // what follows is only present if there are animations on the widget
* utils.transitionInsert(vn.elm, "fade"); * utils.transitionInsert(vn, "fade");
* }, * },
* remove() { * remove() {
* // override with empty function to prevent from removing the node * // override with empty function to prevent from removing the node
@@ -2793,7 +2813,7 @@
* let finalize = () => { * let finalize = () => {
* w4.destroy(); * w4.destroy();
* }; * };
* utils.transitionRemove(vn.elm, "fade", finalize); * utils.transitionRemove(vn, "fade", finalize);
* } * }
* }; * };
* // the pvnode is inserted at the correct position in the div's children * // the pvnode is inserted at the correct position in the div's children
@@ -2834,7 +2854,7 @@
name: "widget", name: "widget",
extraNames: ["props", "keepalive"], extraNames: ["props", "keepalive"],
priority: 100, priority: 100,
atNodeEncounter({ ctx, value, node }) { atNodeEncounter({ ctx, value, node, qweb }) {
ctx.addLine("//WIDGET"); ctx.addLine("//WIDGET");
ctx.rootContext.shouldDefineOwner = true; ctx.rootContext.shouldDefineOwner = true;
ctx.rootContext.shouldDefineQWeb = true; ctx.rootContext.shouldDefineQWeb = true;
@@ -2902,7 +2922,7 @@
} }
let transitionsInsertCode = ""; let transitionsInsertCode = "";
if (transition) { if (transition) {
transitionsInsertCode = `utils.transitionInsert(vn.elm, '${transition}');`; transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
} }
let finalizeWidgetCode = `w${widgetID}.${keepAlive ? "unmount" : "destroy"}();`; let finalizeWidgetCode = `w${widgetID}.${keepAlive ? "unmount" : "destroy"}();`;
if (ref && !keepAlive) { if (ref && !keepAlive) {
@@ -2912,7 +2932,7 @@
finalizeWidgetCode = `let finalize = () => { finalizeWidgetCode = `let finalize = () => {
${finalizeWidgetCode} ${finalizeWidgetCode}
}; };
utils.transitionRemove(vn.elm, '${transition}', finalize);`; utils.transitionRemove(vn, '${transition}', finalize);`;
} }
let createHook = ""; let createHook = "";
let classAttr = node.getAttribute("class"); let classAttr = node.getAttribute("class");
@@ -2987,6 +3007,19 @@
ctx.addLine(`if (!W${widgetID}) {throw new Error('Cannot find the definition of widget "' + widgetKey${widgetID} + '"')}`); ctx.addLine(`if (!W${widgetID}) {throw new Error('Cannot find the definition of widget "' + widgetKey${widgetID} + '"')}`);
ctx.addLine(`w${widgetID} = new W${widgetID}(owner, props${widgetID});`); ctx.addLine(`w${widgetID} = new W${widgetID}(owner, props${widgetID});`);
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`); ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`);
// SLOTS
const slotNodes = node.querySelectorAll("[t-set]");
if (slotNodes.length) {
const slotId = qweb.nextSlotId++;
for (let i = 0, length = slotNodes.length; i < length; i++) {
const slotNode = slotNodes[i];
const key = slotNode.getAttribute("t-set");
slotNode.removeAttribute("t-set");
const slotFn = qweb._compile(`slot_${key}_template`, slotNode);
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
}
ctx.addLine(`w${widgetID}.__owl__.slotId = ${slotId};`);
}
ctx.addLine(`def${defID} = w${widgetID}._prepare();`); ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
// hack: specify empty remove hook to prevent the node from being removed from the DOM // hack: specify empty remove hook to prevent the node from being removed from the DOM
// FIXME: click to re-add widget during remove transition -> leak // FIXME: click to re-add widget during remove transition -> leak
@@ -3036,6 +3069,19 @@
addNodeHook("insert", `if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); }`); addNodeHook("insert", `if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); }`);
} }
}); });
//------------------------------------------------------------------------------
// t-slot
//------------------------------------------------------------------------------
QWeb.addDirective({
name: "slot",
priority: 80,
atNodeEncounter({ ctx, value }) {
const slotKey = ctx.generateID();
ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
ctx.addLine(`c${ctx.parentNode}.push(slot${slotKey}(context.__owl__.parent, extra));`);
return true;
}
});
class Store extends EventBus { class Store extends EventBus {
constructor(config, options = {}) { constructor(config, options = {}) {
@@ -3122,8 +3168,9 @@
return 0; return 0;
} }
let nextID$1 = 1; let nextID$1 = 1;
function connect(mapStateToProps, options = {}) { function connect(Comp, mapStoreToProps, options = {}) {
let hashFunction = options.hashFunction || null; let hashFunction = options.hashFunction || null;
const getStore = options.getStore || (env => env.store);
if (!hashFunction) { if (!hashFunction) {
let deep = "deep" in options ? options.deep : true; let deep = "deep" in options ? options.deep : true;
let defaultRevFunction = deep ? deepRevNumber : revNumber; let defaultRevFunction = deep ? deepRevNumber : revNumber;
@@ -3148,77 +3195,77 @@
return hash; return hash;
}; };
} }
return function (Comp) { const Result = class extends Comp {
const Result = class extends Comp { constructor(parent, props) {
constructor(parent, props) { const env = parent instanceof Component ? parent.env : parent;
const env = parent instanceof Component ? parent.env : parent; const store = getStore(env);
const ownProps = Object.assign({}, props || {}); const ownProps = Object.assign({}, props || {});
const storeProps = mapStateToProps(env.store.state, ownProps, env.store.getters); const storeProps = mapStoreToProps(store.state, ownProps, store.getters);
const mergedProps = Object.assign({}, props || {}, storeProps); const mergedProps = Object.assign({}, props || {}, storeProps);
super(parent, mergedProps); super(parent, mergedProps);
this.__owl__.ownProps = ownProps; this.__owl__.ownProps = ownProps;
this.__owl__.currentStoreProps = storeProps;
this.__owl__.store = store;
this.__owl__.storeHash = hashFunction({
state: store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, {
currentStoreProps: storeProps
});
}
/**
* We do not use the mounted hook here for a subtle reason: we want the
* updates to be called for the parents before the children. However,
* if we use the mounted hook, this will be done in the reverse order.
*/
_callMounted() {
this.__owl__.store.on("update", this, this._checkUpdate);
super._callMounted();
}
willUnmount() {
this.__owl__.store.off("update", this);
super.willUnmount();
}
_checkUpdate() {
const ownProps = this.__owl__.ownProps;
const storeProps = mapStoreToProps(this.__owl__.store.state, ownProps, this.__owl__.store.getters);
const options = {
currentStoreProps: this.__owl__.currentStoreProps
};
const storeHash = hashFunction({
state: this.__owl__.store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, options);
let didChange = options.didChange;
if (storeHash !== this.__owl__.storeHash) {
didChange = true;
this.__owl__.storeHash = storeHash;
}
if (didChange) {
this.__owl__.currentStoreProps = storeProps; this.__owl__.currentStoreProps = storeProps;
this.__owl__.storeHash = hashFunction({ this._updateProps(ownProps, false);
state: env.store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, {
currentStoreProps: storeProps
});
} }
/** }
* We do not use the mounted hook here for a subtle reason: we want the _updateProps(nextProps, forceUpdate, patchQueue) {
* updates to be called for the parents before the children. However, if (this.__owl__.ownProps !== nextProps) {
* if we use the mounted hook, this will be done in the reverse order. this.__owl__.currentStoreProps = mapStoreToProps(this.__owl__.store.state, nextProps, this.__owl__.store.getters);
*/
_callMounted() {
this.env.store.on("update", this, this._checkUpdate);
super._callMounted();
} }
willUnmount() { this.__owl__.ownProps = nextProps;
this.env.store.off("update", this); const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps);
super.willUnmount(); return super._updateProps(mergedProps, forceUpdate, patchQueue);
} }
_checkUpdate() {
const ownProps = this.__owl__.ownProps;
const storeProps = mapStateToProps(this.env.store.state, ownProps, this.env.store.getters);
const options = {
currentStoreProps: this.__owl__.currentStoreProps
};
const storeHash = hashFunction({
state: this.env.store.state,
storeProps: storeProps,
revNumber,
deepRevNumber
}, options);
let didChange = options.didChange;
if (storeHash !== this.__owl__.storeHash) {
didChange = true;
this.__owl__.storeHash = storeHash;
}
if (didChange) {
this.__owl__.currentStoreProps = storeProps;
this._updateProps(ownProps, false);
}
}
_updateProps(nextProps, forceUpdate, patchQueue) {
if (this.__owl__.ownProps !== nextProps) {
this.__owl__.currentStoreProps = mapStateToProps(this.env.store.state, nextProps, this.env.store.getters);
}
this.__owl__.ownProps = nextProps;
const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps);
return super._updateProps(mergedProps, forceUpdate, patchQueue);
}
};
// we assign here a unique name to the resulting anonymous class.
// this is necessary for Owl to be able to properly deduce templates.
// Otherwise, all connected components would have the same name, and then
// each component after the first will necessarily have the same template.
let name = `ConnectedComponent${nextID$1++}`;
Object.defineProperty(Result, "name", { value: name });
return Result;
}; };
// we assign here a unique name to the resulting anonymous class.
// this is necessary for Owl to be able to properly deduce templates.
// Otherwise, all connected components would have the same name, and then
// each component after the first will necessarily have the same template.
let name = `ConnectedComponent${nextID$1++}`;
Object.defineProperty(Result, "name", { value: name });
return Result;
} }
/** /**
@@ -3233,12 +3280,14 @@
* - debounce * - debounce
*/ */
function whenReady(fn) { function whenReady(fn) {
if (document.readyState !== "loading") { return new Promise(function (resolve) {
fn(); if (document.readyState !== "loading") {
} resolve();
else { }
document.addEventListener("DOMContentLoaded", fn, false); else {
} document.addEventListener("DOMContentLoaded", resolve, false);
}
}).then(fn || function () { });
} }
const loadedScripts = {}; const loadedScripts = {};
function loadJS(url) { function loadJS(url) {
@@ -3353,9 +3402,9 @@
exports.connect = connect; exports.connect = connect;
exports.utils = utils; exports.utils = utils;
exports.__info__.version = '0.13.0'; exports.__info__.version = '0.14.0';
exports.__info__.date = '2019-06-08T14:30:19.424Z'; exports.__info__.date = '2019-06-13T10:11:15.338Z';
exports.__info__.hash = '2a19aeb'; exports.__info__.hash = '2701a7d';
exports.__info__.url = 'https://github.com/odoo/owl'; exports.__info__.url = 'https://github.com/odoo/owl';
}(this.owl = this.owl || {})); }(this.owl = this.owl || {}));
+14 -14
View File
@@ -127,22 +127,22 @@ async function makeApp(js, css, xml) {
.map(l => (l === "" ? "" : " " + l)) .map(l => (l === "" ? "" : " " + l))
.join("\n"); .join("\n");
const JS = `async function startApp() { const JS = `
// Loading templates async function loadTemplates() {
let TEMPLATES;
try { try {
TEMPLATES = await owl.utils.loadTemplates('app.xml'); return owl.utils.loadTemplates('app.xml');
} catch(e) { } catch(e) {
document.write(\`This app requires a static server. If you have python installed, try 'python app.py'\`); console.error(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
return;
} }
}
function start([TEMPLATES]) {
// Application code // Application code
${processedJS} ${processedJS}
} }
// wait for DOM ready before starting Promise.all([loadTemplates(), owl.utils.whenReady()]).then(start);
owl.utils.whenReady(startApp);`; `;
zip.file("app.js", JS); zip.file("app.js", JS);
zip.file("app.css", css); zip.file("app.css", css);
@@ -354,13 +354,13 @@ class TabbedEditor extends owl.Component {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
async function start() { async function start() {
document.title = `${document.title} (v${owl.__info__.version})`; document.title = `${document.title} (v${owl.__info__.version})`;
const templates = await owl.utils.loadTemplates("templates.xml"); const [templates] = await Promise.all([
owl.utils.loadTemplates("templates.xml"),
owl.utils.whenReady()
]);
const qweb = new owl.QWeb(templates); const qweb = new owl.QWeb(templates);
const env = { qweb }; const app = new App({ qweb });
owl.utils.whenReady(() => { app.mount(document.body);
const app = new App(env);
app.mount(document.body);
});
} }
start(); start();
+134 -13
View File
@@ -187,7 +187,7 @@ const ANIMATION_CSS = `button {
.flash { .flash {
background-position: center; background-position: center;
transition: background 0.5s; transition: background .6s;
} }
.flash:active { .flash:active {
@@ -208,7 +208,7 @@ const ANIMATION_CSS = `button {
} }
.fade-enter-active, .fade-leave-active { .fade-enter-active, .fade-leave-active {
transition: opacity .5s; transition: opacity .6s;
} }
.fade-enter, .fade-leave-to { .fade-enter, .fade-leave-to {
opacity: 0; opacity: 0;
@@ -233,7 +233,6 @@ const ANIMATION_CSS = `button {
} }
`; `;
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component { const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
constructor() { constructor() {
super(...arguments); super(...arguments);
@@ -384,11 +383,11 @@ class TodoItem extends owl.Component {
state = { isEditing: false }; state = { isEditing: false };
removeTodo() { removeTodo() {
this.env.store.dispatch("removeTodo", this.props.id); this.env.dispatch("removeTodo", this.props.id);
} }
toggleTodo() { toggleTodo() {
this.env.store.dispatch("toggleTodo", this.props.id); this.env.dispatch("toggleTodo", this.props.id);
} }
async editTodo() { async editTodo() {
@@ -420,7 +419,7 @@ class TodoItem extends owl.Component {
if (!value) { if (!value) {
this.removeTodo(this.props.id); this.removeTodo(this.props.id);
} else { } else {
this.env.store.dispatch("editTodo", { this.env.dispatch("editTodo", {
id: this.props.id, id: this.props.id,
title: value title: value
}); });
@@ -432,7 +431,7 @@ class TodoItem extends owl.Component {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// TodoApp // TodoApp
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
function mapStateToProps(state) { function mapStoreToProps(state) {
return { return {
todos: state.todos todos: state.todos
}; };
@@ -470,18 +469,18 @@ class TodoApp extends owl.Component {
if (ev.keyCode === ENTER_KEY) { if (ev.keyCode === ENTER_KEY) {
const title = ev.target.value; const title = ev.target.value;
if (title.trim()) { if (title.trim()) {
this.env.store.dispatch("addTodo", title); this.env.dispatch("addTodo", title);
} }
ev.target.value = ""; ev.target.value = "";
} }
} }
clearCompleted() { clearCompleted() {
this.env.store.dispatch("clearCompleted"); this.env.dispatch("clearCompleted");
} }
toggleAll() { toggleAll() {
this.env.store.dispatch("toggleAll", !this.allChecked); this.env.dispatch("toggleAll", !this.allChecked);
} }
setFilter(filter) { setFilter(filter) {
@@ -489,7 +488,7 @@ class TodoApp extends owl.Component {
} }
} }
const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp); const ConnectedTodoApp = owl.connect(TodoApp, mapStoreToProps);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// App Initialization // App Initialization
@@ -498,7 +497,8 @@ const store = makeStore();
const qweb = new owl.QWeb(TEMPLATES); const qweb = new owl.QWeb(TEMPLATES);
const env = { const env = {
qweb, qweb,
store store,
dispatch: store.dispatch.bind(store),
}; };
const app = new ConnectedTodoApp(env); const app = new ConnectedTodoApp(env);
app.mount(document.body); app.mount(document.body);
@@ -1081,6 +1081,121 @@ const RESPONSIVE_CSS = `body {
} }
`; `;
const SLOTS = `// This example will not work if your browser does not support ESNext class fields
// We show here how slots can be used to create generic components. In this
// example, the Card component is basically only a container, and is created
// by giving it slots, inside the t-widget directive.
class Card extends owl.Component {
state = { fullDisplay: true };
toggleDisplay() {
this.state.fullDisplay = !this.state.fullDisplay;
}
}
class Counter extends owl.Component {
state = {val: 1};
inc() {
this.state.val++;
}
}
// Main root widget
class App extends owl.Component {
widgets = {Card, Counter};
state = {a: 1, b: 3};
inc(key, delta) {
this.state[key] += delta;
}
}
// Application setup
const qweb = new owl.QWeb(TEMPLATES);
const app = new App({ qweb });
app.mount(document.body);`;
const SLOTS_XML = `<templates>
<div t-name="Card" class="card" t-att-class="state.fullDisplay ? 'full' : 'small'">
<div class="card-title">
<t t-esc="props.title"/><button t-on-click="toggleDisplay">Toggle</button>
</div>
<t t-if="state.fullDisplay">
<div class="card-content" >
<t t-slot="content"/>
</div>
<div class="card-footer">
<t t-slot="footer"/>
</div>
</t>
</div>
<div t-name="Counter">
<t t-esc="state.val"/><button t-on-click="inc">Inc</button>
</div>
<div t-name="App" class="main">
<t t-widget="Card" title="'Title card A'">
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
</t>
<t t-widget="Card" title="'Title card B'">
<div t-set="content">
<div>Card 2... [<t t-esc="state.b"/>]</div>
<t t-widget="Counter"/>
</div>
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
</t>
</div>
</templates>`;
const SLOTS_CSS = `.main {
display: flex;
}
.card {
display: flex;
flex-direction: column;
background-color: #eeeeee;
width: 200px;
height: 100px;
margin: 10px;
border: 1px solid gray;
}
.card.full {
height: 100px;
}
.card.small {
height: 25px;
}
.card-title {
flex: 0 0 25px;
font-weight: bold;
background-color: darkcyan;
color: white;
padding: 2px;
}
.card-title button {
float: right;
}
.card-content {
flex: 1 1 auto;
padding: 5px;
border-top: 1px solid white;
}
.card-footer {
border-top: 1px solid white;
}`;
const EMPTY = `class App extends owl.Component { const EMPTY = `class App extends owl.Component {
} }
@@ -1112,7 +1227,7 @@ export const SAMPLES = [
description: "Animations", description: "Animations",
code: ANIMATION, code: ANIMATION,
xml: ANIMATION_XML, xml: ANIMATION_XML,
css: ANIMATION_CSS, css: ANIMATION_CSS
}, },
{ {
description: "Lifecycle demo", description: "Lifecycle demo",
@@ -1131,6 +1246,12 @@ export const SAMPLES = [
css: RESPONSIVE_CSS, css: RESPONSIVE_CSS,
xml: RESPONSIVE_XML xml: RESPONSIVE_XML
}, },
{
description: "Slots",
code: SLOTS,
xml: SLOTS_XML,
css: SLOTS_CSS
},
{ {
description: "Empty", description: "Empty",
code: EMPTY code: EMPTY