2019-04-16 17:37:17 +02:00
const HELLO _WORLD = ` const {Component, QWeb} = owl;
2019-04-01 09:40:03 +02:00
2019-04-16 17:37:17 +02:00
class App extends Component {
2019-03-31 09:33:05 +02:00
constructor() {
super(...arguments);
2019-04-01 09:40:03 +02:00
this.template = "demo.hello";
2019-03-29 10:14:02 +01:00
}
}
2019-04-01 09:40:03 +02:00
const qweb = new QWeb(TEMPLATES);
2019-04-16 17:37:17 +02:00
const app = new App({qweb}, { name: "World" });
app.mount(document.body);
2019-03-29 10:14:02 +01:00
` ;
2019-04-01 09:40:03 +02:00
const HELLO _WORLD _XML = ` <templates>
<div t-name="demo.hello" class="hello">
Hello <t t-esc="props.name"/>
</div>
</templates> ` ;
2019-03-29 15:35:09 +01:00
const HELLO _WORLD _CSS = ` .hello {
color: darkred;
font-size: 30px;
} ` ;
2019-03-29 10:14:02 +01:00
const HELLO _WORLD _ESNEXT = ` // This example will not work if your browser does not support ESNext Class Fields
2019-04-16 17:37:17 +02:00
const {Component, QWeb} = owl;
2019-03-28 17:31:25 +01:00
2019-04-01 09:40:03 +02:00
class HelloWorld extends Component {
template = "demo.hello";
}
2019-03-28 17:31:25 +01:00
2019-04-01 09:40:03 +02:00
const qweb = new QWeb(TEMPLATES);
const hello = new HelloWorld({qweb}, { name: "World" });
hello.mount(document.body); ` ;
2019-03-28 17:31:25 +01:00
2019-04-16 17:37:17 +02:00
const HELLO _WORLD _ES5 = ` const { Component, QWeb } = owl;
2019-04-04 13:09:52 +02:00
function HelloWorld(env, props) {
var obj = new Component(env, props);
Object.setPrototypeOf(obj, HelloWorld.prototype);
obj.template = "demo.hello";
obj.state = { greeting: "Hello" };
return obj;
}
HelloWorld.prototype = Object.create(Component.prototype);
// we show here how to add methods to sub components
HelloWorld.prototype.changeGreeting = function() {
var newGreeting = this.state.greeting === "Hello" ? "Hi" : "Hello";
2019-04-17 11:44:35 +02:00
this.state.greeting = newGreeting;
2019-04-04 13:09:52 +02:00
};
const qweb = new QWeb(TEMPLATES);
const hello = new HelloWorld({ qweb }, { name: "ES5 World" });
hello.mount(document.body);
` ;
const HELLO _WORLD _ES5 _XML = ` <templates>
<div t-name="demo.hello" class="hello" t-on-click="changeGreeting">
<t t-esc="state.greeting"/> <t t-esc="props.name"/>
</div>
</templates> ` ;
2019-04-16 17:37:17 +02:00
const WIDGET _COMPOSITION = ` class Counter extends owl.Component {
2019-03-28 17:31:25 +01:00
constructor(parent, props) {
super(parent, props);
2019-04-01 09:40:03 +02:00
this.template="counter";
2019-03-28 17:31:25 +01:00
this.state = {
value: props.initialState || 0
};
}
increment(delta) {
2019-04-17 11:44:35 +02:00
this.state.value += delta;
2019-03-28 17:31:25 +01:00
}
}
2019-04-16 17:37:17 +02:00
class App extends owl.Component {
2019-03-31 09:33:05 +02:00
constructor() {
super(...arguments);
2019-04-01 09:40:03 +02:00
this.template="app";
2019-03-29 10:14:02 +01:00
this.widgets = { Counter };
}
2019-03-28 17:31:25 +01:00
}
const env = {
2019-04-16 17:37:17 +02:00
qweb: new owl.QWeb(TEMPLATES)
2019-03-28 17:31:25 +01:00
};
const app = new App(env);
app.mount(document.body);
` ;
2019-04-01 09:40:03 +02:00
const WIDGET _COMPOSITION _XML = ` <templates>
<div t-name="counter">
<button t-on-click="increment(-1)">-</button>
<span style="font-weight:bold">Value: <t t-esc="state.value"/></span>
<button t-on-click="increment(1)">+</button>
</div>
<div t-name="app">
<t t-widget="Counter" t-props="{initialState: 1}"/>
<t t-widget="Counter" t-props="{initialState: 42}"/>
</div>
</templates> ` ;
2019-04-16 17:37:17 +02:00
const LIFECYCLE _DEMO = ` const { Component, QWeb } = owl;
2019-04-09 14:50:40 +02:00
class HookWidget extends Component {
constructor() {
super(...arguments);
this.template = "demo.hookwidget";
this.state = { n: 0 };
console.log("constructor");
}
async willStart() {
console.log("willstart");
}
mounted() {
console.log("mounted");
}
async willUpdateProps(nextProps) {
console.log("willUpdateProps", nextProps);
}
willPatch() {
console.log("willPatch");
}
patched() {
console.log("patched");
}
willUnmount() {
console.log("willUnmount");
}
increment() {
2019-04-17 11:44:35 +02:00
this.state.n++;
2019-04-09 14:50:40 +02:00
}
}
class ParentWidget extends Component {
constructor() {
super(...arguments);
this.widgets = { HookWidget };
this.template = "demo.parentwidget";
this.state = { n: 0, flag: true };
}
increment() {
2019-04-17 11:44:35 +02:00
this.state.n++;
2019-04-09 14:50:40 +02:00
}
toggleSubWidget() {
2019-04-17 11:44:35 +02:00
this.state.flag = !this.state.flag;
2019-04-09 14:50:40 +02:00
}
}
const qweb = new QWeb(TEMPLATES);
const widget = new ParentWidget({ qweb });
widget.mount(document.body);
` ;
const LIFECYCLE _DEMO _XML = ` <templates>
<div t-name="demo.parentwidget">
<button t-on-click="increment">Increment</button>
<button t-on-click="toggleSubWidget">ToggleSubWidget</button>
<div t-if="state.flag">
<t t-widget="HookWidget" t-props="{n:state.n}"/>
</div>
</div>
<div t-name="demo.hookwidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div>
</templates> ` ;
2019-03-29 13:23:36 +01:00
const BENCHMARK _APP = ` //------------------------------------------------------------------------------
// Generating demo data
//------------------------------------------------------------------------------
const messages = [];
const authors = ["Aaron", "David", "Vincent"];
const content = [
"Lorem ipsum dolor sit amet",
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem",
"Excepteur sint occaecat cupidatat non proident"
];
function chooseRandomly(array) {
const index = Math.floor(Math.random() * array.length);
return array[index];
}
for (let i = 1; i < 16000; i++) {
messages.push({
id: i,
author: chooseRandomly(authors),
msg: \` \$ {i}: \$ {chooseRandomly(content)} \` ,
likes: 0
});
}
//------------------------------------------------------------------------------
// Counter Widget
//------------------------------------------------------------------------------
2019-04-16 17:37:17 +02:00
class Counter extends owl.Component {
2019-03-29 13:23:36 +01:00
constructor(parent, props) {
super(parent, props);
2019-03-29 15:35:09 +01:00
this.template = "counter";
2019-03-29 13:23:36 +01:00
this.state = {
counter: props.initialState || 0
};
}
increment(delta) {
2019-04-17 11:44:35 +02:00
this.state.counter += delta;
2019-03-29 13:23:36 +01:00
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
2019-04-16 17:37:17 +02:00
class Message extends owl.Component {
2019-03-31 09:33:05 +02:00
constructor() {
super(...arguments);
2019-03-29 15:35:09 +01:00
this.template = "message";
2019-03-29 13:23:36 +01:00
this.widgets = { Counter };
}
removeMessage() {
this.trigger("remove_message", {
id: this.props.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
2019-04-16 17:37:17 +02:00
class App extends owl.Component {
2019-03-31 09:33:05 +02:00
constructor() {
super(...arguments);
2019-03-29 15:35:09 +01:00
this.template = "root";
2019-03-29 13:23:36 +01:00
this.widgets = { Message };
this.state = {
messages: messages.slice(0, 10)
};
}
setMessageCount(n) {
2019-04-17 11:44:35 +02:00
this.state.messages = messages.slice(0,n);
2019-03-29 13:23:36 +01:00
}
removeMessage(data) {
const index = messages.findIndex(m => m.id === data.id);
2019-04-17 11:44:35 +02:00
this.state.messages.splice(index, 1);
2019-03-29 13:23:36 +01:00
}
increment(delta) {
const n = this.state.messages.length + delta;
this.setMessageCount(n);
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
2019-03-29 16:48:16 +01:00
const env = {
2019-04-16 17:37:17 +02:00
qweb: new owl.QWeb(TEMPLATES)
2019-03-29 16:48:16 +01:00
};
2019-03-29 13:23:36 +01:00
const app = new App(env);
app.mount(document.body);
` ;
const BENCHMARK _APP _CSS = ` .main {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: grid;
2019-04-01 14:08:23 +02:00
grid-template-columns: 220px 1fr;
2019-03-29 13:23:36 +01:00
}
.left-thing {
background-color: gray;
padding: 20px;
}
.left-thing button {
width: 100%;
}
.left-thing .counter span {
color: white;
}
.left-thing .counter button {
width: 40px;
}
.right-thing {
padding: 20px;
overflow: auto;
}
/* Message widget */
.message .author {
font-weight: bold;
}
.message {
width: 400px;
background-color: lightblue;
margin: 10px 5px;
border-radius: 5px;
padding: 5px;
} ` ;
2019-03-29 15:35:09 +01:00
const BENCHMARK _APP _XML = ` <templates>
<div t-name="root" class="main">
<div class="left-thing">
<div class="counter">
<button t-on-click="increment(-1)">-</button>
<span style="font-weight:bold">Value: <t t-esc="state.messages.length"/></span>
<button t-on-click="increment(1)">+</button>
</div>
<button t-on-click="setMessageCount(10)">10 messages</button>
<button t-on-click="setMessageCount(20)">20 messages</button>
<button t-on-click="setMessageCount(500)">500 messages</button>
<button t-on-click="setMessageCount(1000)">1000 messages</button>
<button t-on-click="setMessageCount(5000)">5000 messages</button>
<button t-on-click="setMessageCount(15000)">15000 messages</button>
</div>
<div class="right-thing">
<div class="content">
<t t-foreach="state.messages" t-as="message">
<t t-widget="Message" t-att-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
</t>
</div>
</div>
</div>
<div t-name="message" class="message">
<span class="author"><t t-esc="props.author"/></span>
<span class="msg"><t t-esc="props.msg"/></span>
<button class="remove" t-on-click="removeMessage">Remove</button>
<t t-widget="Counter" t-props="{initialState: props.id}"/>
</div>
<div t-name="counter">
<button t-on-click="increment(-1)">-</button>
<span style="font-weight:bold">Value: <t t-esc="state.counter"/></span>
<button t-on-click="increment(1)">+</button>
</div>
</templates> ` ;
2019-04-16 17:37:17 +02:00
const STATE _MANAGEMENT = ` const { Component, QWeb, Store, connect } = owl;
2019-04-01 14:08:23 +02:00
//------------------------------------------------------------------------------
// Store Definition
//------------------------------------------------------------------------------
const actions = {
addTodo({ commit }, title) {
commit("addTodo", title);
},
removeTodo({ commit }, id) {
commit("removeTodo", id);
},
2019-04-16 10:36:17 +02:00
toggleTodo({ commit }, id) {
2019-04-01 14:08:23 +02:00
commit("toggleTodo", id);
},
clearCompleted({ state, commit }) {
state.todos
.filter(todo => todo.completed)
.forEach(todo => {
commit("removeTodo", todo.id);
});
}
};
const mutations = {
2019-04-16 10:36:17 +02:00
addTodo({ state }, title) {
2019-04-01 14:08:23 +02:00
const id = state.nextId++;
const todo = { id, title, completed: false };
state.todos.push(todo);
},
2019-04-16 10:36:17 +02:00
removeTodo({ state }, id) {
2019-04-01 14:08:23 +02:00
const index = state.todos.findIndex(t => t.id === id);
state.todos.splice(index, 1);
},
2019-04-16 10:36:17 +02:00
toggleTodo({ state }, id) {
2019-04-01 14:08:23 +02:00
const todo = state.todos.find(t => t.id === id);
todo.completed = !todo.completed;
}
};
const state = { todos: [], nextId: 1 };
//------------------------------------------------------------------------------
// TodoList root widget
//------------------------------------------------------------------------------
class TodoList extends Component {
constructor() {
super(...arguments);
this.template = "todoapp";
}
addTodo(ev) {
if (ev.keyCode === 13) {
const title = ev.target.value;
if (title.trim()) {
this.env.store.dispatch("addTodo", title);
}
ev.target.value = "";
}
}
toggleTodo(todo) {
this.env.store.dispatch("toggleTodo", todo.id);
}
removeTodo(todo) {
this.env.store.dispatch("removeTodo", todo.id);
}
}
2019-04-16 10:36:17 +02:00
const App = connect(state => state)(TodoList);
2019-04-01 14:08:23 +02:00
//------------------------------------------------------------------------------
// App Initialization
//------------------------------------------------------------------------------
const store = new Store({ state, actions, mutations });
const qweb = new QWeb(TEMPLATES);
const env = {
qweb,
store
};
const app = new App(env);
app.mount(document.body);
` ;
const STATE _MANAGEMENT _XML = ` <templates>
<div t-name="todoapp">
<input autofocus="true" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
<ul>
<li class="todo" t-foreach="props.todos" t-as="todo">
2019-04-16 10:36:17 +02:00
<input type="checkbox" t-att-checked="todo.completed" t-on-change="toggleTodo(todo)"/>
2019-04-01 14:08:23 +02:00
<span t-att-class="{completed: todo.completed}"><t t-esc="todo.id"/>. <t t-esc="todo.title"/></span>
<span class="action" t-on-click="removeTodo(todo)">(remove)</span>
</li>
</ul>
</div>
2019-04-16 10:36:17 +02:00
</templates> ` ;
2019-04-01 14:08:23 +02:00
const STATE _MANAGEMENT _CSS = ` .action {
cursor: pointer;
margin: 0 5px;
}
.completed {
text-decoration: line-through;
}
` ;
2019-03-28 17:31:25 +01:00
2019-04-16 17:37:17 +02:00
const RESPONSIVE = ` const { Component, QWeb, utils } = owl;
2019-04-02 16:15:29 +02:00
class SubWidget extends Component {
constructor() {
super(...arguments);
this.template = "subwidget";
}
}
class ResponsiveWidget extends Component {
constructor() {
super(...arguments);
this.template = "responsivewidget";
this.widgets = { SubWidget };
}
}
function isMobile() {
return window.innerWidth <= 768;
}
const env = {
qweb: new QWeb(TEMPLATES),
isMobile: isMobile()
};
const widget = new ResponsiveWidget(env);
widget.mount(document.body);
window.addEventListener(
"resize",
utils.debounce(function() {
const _isMobile = isMobile();
if (_isMobile !== env.isMobile) {
widget.updateEnv({ isMobile: _isMobile });
}
}, 20)
);
` ;
const RESPONSIVE _XML = ` <templates>
<div t-name="responsivewidget">
<div class="info">
<span class="mobile" t-if="env.isMobile">Mobile</span>
<span class="desktop" t-else="1">Desktop</span>
mode
</div>
<t t-widget="SubWidget" t-if="!env.isMobile"/>
</div>
<div t-name="subwidget" class="subwidget">
This widget is only instantiated in desktop mode. It will be destroyed
and recreated if the mode changes from destop to mobile, and back to desktop
</div>
</templates>
` ;
const RESPONSIVE _CSS = ` .info {
font-size: 30px;
}
.desktop {
color: green;
}
.mobile {
color: blue;
}
.subwidget {
margin: 30px;
} ` ;
2019-04-16 17:37:17 +02:00
const EMPTY = ` const {Component, QWeb} = owl;
2019-04-01 16:26:19 +02:00
class Widget extends Component {
}
const qweb = new QWeb(TEMPLATES);
const widget = new Widget({qweb});
widget.mount(document.body);
` ;
2019-03-28 17:31:25 +01:00
export const SAMPLES = [
{
description : "Hello World" ,
2019-03-29 15:35:09 +01:00
code : HELLO _WORLD ,
2019-04-01 09:40:03 +02:00
xml : HELLO _WORLD _XML ,
2019-03-29 15:35:09 +01:00
css : HELLO _WORLD _CSS
2019-03-28 17:31:25 +01:00
} ,
2019-03-29 10:14:02 +01:00
{
description : "Hello World (ESNext)" ,
2019-03-29 15:35:09 +01:00
code : HELLO _WORLD _ESNEXT ,
2019-04-01 09:40:03 +02:00
xml : HELLO _WORLD _XML ,
2019-03-29 15:35:09 +01:00
css : HELLO _WORLD _CSS
2019-03-29 10:14:02 +01:00
} ,
2019-04-04 13:09:52 +02:00
{
description : "Hello World (ES5)" ,
code : HELLO _WORLD _ES5 ,
xml : HELLO _WORLD _ES5 _XML ,
css : HELLO _WORLD _CSS
} ,
2019-03-28 17:31:25 +01:00
{
description : "Widget Composition" ,
2019-04-01 09:40:03 +02:00
code : WIDGET _COMPOSITION ,
xml : WIDGET _COMPOSITION _XML
2019-03-28 17:31:25 +01:00
} ,
2019-04-09 14:50:40 +02:00
{
description : "Lifecycle demo" ,
code : LIFECYCLE _DEMO ,
xml : LIFECYCLE _DEMO _XML
} ,
2019-03-28 17:31:25 +01:00
{
description : "Benchmark application" ,
2019-03-29 13:23:36 +01:00
code : BENCHMARK _APP ,
2019-03-29 15:35:09 +01:00
css : BENCHMARK _APP _CSS ,
xml : BENCHMARK _APP _XML
2019-03-28 17:31:25 +01:00
} ,
{
description : "State management app" ,
2019-04-01 14:08:23 +02:00
code : STATE _MANAGEMENT ,
css : STATE _MANAGEMENT _CSS ,
xml : STATE _MANAGEMENT _XML
2019-03-28 17:31:25 +01:00
} ,
2019-04-02 16:15:29 +02:00
{
description : "Responsive app" ,
code : RESPONSIVE ,
css : RESPONSIVE _CSS ,
xml : RESPONSIVE _XML
} ,
2019-03-28 17:31:25 +01:00
{
description : "Empty" ,
code : EMPTY
}
] ;