[IMP] playground: update Owl to 0.9.0

This commit is contained in:
Géry Debongnie
2019-05-03 12:28:59 +02:00
parent 7642ce4e2c
commit 62469c4264
14 changed files with 625 additions and 537 deletions
+254 -208
View File
@@ -965,23 +965,6 @@
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Attach a child widget to a given html element
*
* This is most of the time not necessary, since widgets should primarily be
* created/managed with the t-widget directive in a qweb template. However,
* for the cases where we need more control, this method will do what is
* necessary to make sure all the proper hooks are called (for example,
* mounted/willUnmount)
*
* Note that this method makes a few assumptions:
* - the child widget is indeed a child of the current widget
* - the target is inside the dom of the current widget (typically a ref)
*/
attachChild(child, target) {
target.appendChild(child.el);
child.__mount();
}
async mount(target) {
const vnode = await this._prepare();
if (this.__owl__.isDestroyed) {
@@ -991,64 +974,94 @@
this._patch(vnode);
target.appendChild(this.el);
if (document.body.contains(target)) {
this._visitSubTree(w => {
if (!w.__owl__.isMounted && this.el.contains(w.el)) {
w.__owl__.isMounted = true;
w.mounted();
return true;
}
return false;
});
this._callMounted();
}
}
_callMounted() {
const children = this.__owl__.children;
for (let id in children) {
const comp = children[id];
if (!comp.__owl__.isMounted && this.el.contains(comp.el)) {
comp._callMounted();
}
}
this.__owl__.isMounted = true;
this.mounted();
}
_callWillUnmount() {
this.willUnmount();
this.__owl__.isMounted = false;
const children = this.__owl__.children;
for (let id in children) {
const comp = children[id];
if (comp.__owl__.isMounted) {
comp._callWillUnmount();
}
}
}
unmount() {
if (this.el) {
this._visitSubTree(w => {
if (w.__owl__.isMounted) {
w.willUnmount();
w.__owl__.isMounted = false;
return true;
}
return false;
});
if (this.__owl__.isMounted) {
this._callWillUnmount();
this.el.remove();
}
}
async render(force = false) {
async render(force = false, patchQueue) {
if (this.__owl__.isDestroyed) {
return;
}
const renderVDom = this._render(force);
const shouldCallPatchHooks = !patchQueue;
if (shouldCallPatchHooks) {
patchQueue = [];
}
const renderVDom = this._render(force, patchQueue);
const renderId = this.__owl__.renderId;
const vnode = await renderVDom;
if (renderId === this.__owl__.renderId) {
// we only update the vnode and the actual DOM if no other rendering
// occurred between now and when the render method was initially called.
if (shouldCallPatchHooks) {
for (let i = 0; i < patchQueue.length; i++) {
const c = patchQueue[i];
c.__owl__.willPatchVal = c.willPatch();
}
}
this._patch(vnode);
if (shouldCallPatchHooks) {
for (let i = patchQueue.length - 1; i >= 0; i--) {
const c = patchQueue[i];
c.patched(c.__owl__.willPatchVal);
}
}
}
}
destroy() {
if (!this.__owl__.isDestroyed) {
for (let id in this.__owl__.children) {
this.__owl__.children[id].destroy();
const el = this.el;
this._destroy(this.__owl__.parent);
if (el) {
el.remove();
}
if (this.__owl__.isMounted) {
this.willUnmount();
}
if (this.el) {
this.el.remove();
this.__owl__.isMounted = false;
delete this.__owl__.vnode;
}
if (this.__owl__.parent) {
let id = this.__owl__.id;
delete this.__owl__.parent.__owl__.children[id];
this.__owl__.parent = null;
}
this.clear();
this.__owl__.isDestroyed = true;
}
}
_destroy(parent) {
const isMounted = this.__owl__.isMounted;
if (isMounted) {
this.willUnmount();
this.__owl__.isMounted = false;
}
const children = Object.values(this.__owl__.children);
for (let child of children) {
child._destroy(this);
}
if (parent) {
let id = this.__owl__.id;
delete parent.__owl__.children[id];
this.__owl__.parent = null;
}
this.clear();
this.__owl__.isDestroyed = true;
delete this.__owl__.vnode;
}
shouldUpdate(nextProps) {
return true;
}
@@ -1070,61 +1083,61 @@
await this.render(true);
}
}
async updateProps(nextProps, forceUpdate = false) {
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
return shouldUpdate ? this._updateProps(nextProps) : Promise.resolve();
}
set(target, key, value) {
this.__owl__.observer.set(target, key, value);
}
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
async _updateProps(nextProps) {
await this.willUpdateProps(nextProps);
this.props = nextProps;
await this.render();
async _updateProps(nextProps, forceUpdate = false, patchQueue) {
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
if (shouldUpdate) {
await this.willUpdateProps(nextProps);
this.props = nextProps;
await this.render(false, patchQueue);
}
}
_patch(vnode) {
this.__owl__.renderPromise = null;
if (this.__owl__.vnode) {
const isMounted = this.__owl__.isMounted;
const snapshot = isMounted && this.willPatch();
this.__owl__.vnode = patch(this.__owl__.vnode, vnode);
if (isMounted) {
this.patched(snapshot);
}
}
else {
this.__owl__.vnode = patch(document.createElement(vnode.sel), vnode);
}
}
async _prepare() {
_prepare() {
this.__owl__.renderProps = this.props;
this.__owl__.renderPromise = this.willStart().then(() => {
if (this.__owl__.isDestroyed) {
return Promise.resolve(h("div"));
}
this.__owl__.isStarted = true;
if (this.inlineTemplate) {
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
}
this._observeState();
return this._render();
});
this.__owl__.renderPromise = this._prepareAndRender();
return this.__owl__.renderPromise;
}
async _render(force = false) {
async _prepareAndRender() {
await this.willStart();
if (this.__owl__.isDestroyed) {
return Promise.resolve(h("div"));
}
this.__owl__.isStarted = true;
if (this.inlineTemplate) {
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
}
this.__owl__.render = this.env.qweb.render.bind(this.env.qweb, this.inlineTemplate || this.template);
this._observeState();
return this._render();
}
async _render(force = false, patchQueue = []) {
if (this.__owl__.isMounted) {
patchQueue.push(this);
}
this.__owl__.renderId++;
const promises = [];
const template = this.inlineTemplate || this.template;
if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = false;
}
let vnode = this.env.qweb.render(template, this, {
let vnode = this.__owl__.render(this, {
promises,
handlers: this.__owl__.boundHandlers,
forceUpdate: force
forceUpdate: force,
patchQueue
});
if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = true;
@@ -1144,32 +1157,17 @@
*/
_mount(vnode, elm) {
this.__owl__.vnode = patch(elm, vnode);
this.__mount();
if (this.__owl__.parent &&
this.__owl__.parent.__owl__.isMounted &&
!this.__owl__.isMounted) {
this._callMounted();
}
return this.__owl__.vnode;
}
__mount() {
if (this.__owl__.isMounted) {
return;
}
this._observeState();
if (this.__owl__.parent) {
if (this.__owl__.parent.__owl__.isMounted) {
this.__owl__.isMounted = true;
this.mounted();
const children = this.__owl__.children;
for (let id in children) {
children[id].__mount();
}
}
}
}
_visitSubTree(callback) {
const shouldVisitChildren = callback(this);
if (shouldVisitChildren) {
const children = this.__owl__.children;
for (let id in children) {
children[id]._visitSubTree(callback);
}
if (!this.__owl__.isMounted) {
this.__owl__.isMounted = true;
this.mounted();
}
}
_observeState() {
@@ -1200,6 +1198,62 @@
];
const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g;
function parseXML(xml) {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
}
return doc;
}
const UTILS = {
h: h,
getFragment(str) {
const temp = document.createElement("template");
temp.innerHTML = str;
return temp.content;
},
objectToAttrString(obj) {
let classes = [];
for (let k in obj) {
if (obj[k]) {
classes.push(k);
}
}
return classes.join(" ");
},
nextFrame(cb) {
requestAnimationFrame(() => requestAnimationFrame(cb));
},
transitionCreate(elm, name) {
elm.classList.add(name + "-enter");
elm.classList.add(name + "-enter-active");
},
transitionInsert(elm, name) {
const finalize = () => {
elm.classList.remove(name + "-enter-active");
elm.classList.remove(name + "-enter-to");
};
elm.addEventListener("transitionend", finalize);
this.nextFrame(() => {
elm.classList.remove(name + "-enter");
elm.classList.add(name + "-enter-to");
});
},
transitionRemove(elm, name, rm) {
elm.classList.add(name + "-leave");
elm.classList.add(name + "-leave-active");
elm.addEventListener("transitionend", () => {
elm.classList.remove(name + "-leave-active");
elm.classList.remove(name + "-enter-to");
rm();
});
this.nextFrame(() => {
elm.classList.remove(name + "-leave");
elm.classList.add(name + "-leave-to");
});
}
};
//------------------------------------------------------------------------------
// Compilation Context
//------------------------------------------------------------------------------
@@ -1335,26 +1389,9 @@
//------------------------------------------------------------------------------
class QWeb {
constructor(data) {
this.processedTemplates = {};
this.templates = {};
this.directives = [];
this.utils = {
h: h,
getFragment(str) {
const temp = document.createElement("template");
temp.innerHTML = str;
return temp.content;
},
objectToAttrString(obj) {
let classes = [];
for (let k in obj) {
if (obj[k]) {
classes.push(k);
}
}
return classes.join(" ");
}
};
this.utils = UTILS;
this.directiveNames = {
as: 1,
name: 1,
@@ -1364,7 +1401,8 @@
props: 1,
key: 1,
keepalive: 1,
debug: 1
debug: 1,
log: 1
};
[
forEachDirective,
@@ -1377,6 +1415,9 @@
callDirective,
onDirective,
refDirective,
transitionDirective,
debugDirective,
logDirective,
widgetDirective
].forEach(d => this.addDirective(d));
if (data) {
@@ -1392,26 +1433,30 @@
* Add a template to the internal template map. Note that it is not
* immediately compiled.
*/
addTemplate(name, template, allowDuplicates = false) {
if (name in this.processedTemplates) {
if (allowDuplicates) {
return;
}
else {
throw new Error(`Template ${name} already defined`);
}
addTemplate(name, xmlString, allowDuplicates = false) {
if (name in this.templates && allowDuplicates) {
return;
}
const parser = new DOMParser();
const doc = parser.parseFromString(template, "text/xml");
const doc = parseXML(xmlString);
if (!doc.firstChild) {
throw new Error("Invalid template (should not be empty)");
}
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
this._addTemplate(name, doc.firstChild);
}
_addTemplate(name, elem) {
if (name in this.templates) {
throw new Error(`Template ${name} already defined`);
}
let elem = doc.firstChild;
this._processTemplate(elem);
this.processedTemplates[name] = elem;
const template = {
elem,
fn: (context, extra) => {
const compiledFunction = this._compile(name, elem);
template.fn = compiledFunction;
return compiledFunction.call(this, context, extra);
}
};
this.templates[name] = template;
}
_processTemplate(elem) {
let tbranch = elem.querySelectorAll("[t-elif], [t-else]");
@@ -1453,19 +1498,14 @@
* the name given by the t-name attribute.
*/
loadTemplates(xmlstr) {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlstr, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
}
const doc = parseXML(xmlstr);
const templates = doc.getElementsByTagName("templates")[0];
if (!templates) {
return;
}
for (let elem of templates.children) {
const name = elem.getAttribute("t-name");
this._processTemplate(elem);
this.processedTemplates[name] = elem;
this._addTemplate(name, elem);
}
}
/**
@@ -1474,20 +1514,16 @@
* @param {string} name the template should already have been added
*/
render(name, context = {}, extra = null) {
if (!(name in this.processedTemplates)) {
const template = this.templates[name];
if (!template) {
throw new Error(`Template ${name} does not exist`);
}
const template = this.templates[name] || this._compile(name);
return template.call(this, context, extra);
return template.fn.call(this, context, extra);
}
_compile(name) {
if (name in this.templates) {
return this.templates[name];
}
const mainNode = this.processedTemplates[name];
const isDebug = mainNode.attributes.hasOwnProperty("t-debug");
_compile(name, elem) {
const isDebug = elem.attributes.hasOwnProperty("t-debug");
const ctx = new Context(name);
this._compileNode(mainNode, ctx);
this._compileNode(elem, ctx);
if (ctx.shouldProtectContext) {
ctx.code.unshift(" context = Object.create(context);");
}
@@ -1500,9 +1536,6 @@
throw new Error("A template should have one root node");
}
ctx.addLine(`return vn${ctx.rootNode};`);
if (isDebug) {
ctx.code.unshift(" debugger");
}
let template;
try {
template = new Function("context", "extra", ctx.code.join("\n"));
@@ -1511,9 +1544,9 @@
throw new Error(`Invalid generated code while compiling template '${ctx.templateName.replace(/`/g, "'")}': ${e.message}`);
}
if (isDebug) {
console.log(`Template: ${this.processedTemplates[name].outerHTML}\nCompiled code:\n` + template.toString());
console.log(`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
template.toString());
}
this.templates[name] = template;
return template;
}
/**
@@ -1890,7 +1923,7 @@
throw new Error("Invalid tag for t-call directive (should be 't')");
}
const subTemplate = node.getAttribute("t-call");
const nodeTemplate = qweb.processedTemplates[subTemplate];
const nodeTemplate = qweb.templates[subTemplate];
if (!nodeTemplate) {
throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
}
@@ -1918,7 +1951,7 @@
.subContext("caller", nodeCopy)
.subContext("variables", Object.create(vars))
.subContext("definedVariables", Object.create(definedVariables));
qweb._compileNode(nodeTemplate, subCtx);
qweb._compileNode(nodeTemplate.elem, subCtx);
// close new scope
if (hasNewVariables) {
ctx.dedent();
@@ -1952,7 +1985,19 @@
ctx.addLine(`context.${name} = _${keysID}[i];`);
ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
const nodeCopy = node.cloneNode(true);
if (nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key")) {
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
if (!shouldWarn && node.tagName === "t") {
if (node.hasAttribute("t-widget") && !node.hasAttribute("t-key")) {
shouldWarn = true;
}
if (!shouldWarn &&
node.children.length === 1 &&
node.children[0].tagName !== 't' &&
!node.children[0].hasAttribute("t-key")) {
shouldWarn = true;
}
}
if (shouldWarn) {
console.warn(`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')`);
}
nodeCopy.removeAttribute("t-foreach");
@@ -1989,13 +2034,47 @@
const refDirective = {
name: "ref",
priority: 95,
atNodeCreation({ ctx, node }) {
let ref = node.getAttribute("t-ref");
ctx.addLine(`p${ctx.parentNode}.hook = {
create: (_, n) => context.refs[${ctx.formatExpression(ref)}] = n.elm,
atNodeCreation({ ctx, nodeID, value }) {
const refKey = `ref${ctx.generateID()}`;
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`);
ctx.addLine(`p${nodeID}.hook = {
create: (_, n) => context.refs[${refKey}] = n.elm,
};`);
}
};
const transitionDirective = {
name: "transition",
priority: 96,
atNodeCreation({ ctx, value }) {
let name = value;
ctx.addLine(`p${ctx.parentNode}.hook = {
create: (_, n) => {
this.utils.transitionCreate(n.elm, '${name}');
},
insert: vn => {
this.utils.transitionInsert(vn.elm, '${name}');
},
remove: (vn, rm) => {
this.utils.transitionRemove(vn.elm, '${name}', rm);
}
};`);
}
};
const debugDirective = {
name: "debug",
priority: 99,
atNodeEncounter({ ctx }) {
ctx.addLine("debugger;");
}
};
const logDirective = {
name: "log",
priority: 99,
atNodeEncounter({ ctx, value }) {
const expr = ctx.formatExpression(value);
ctx.addLine(`console.log(${expr})`);
}
};
const widgetDirective = {
name: "widget",
priority: 100,
@@ -2043,7 +2122,7 @@
// check if we can reuse current rendering promise
ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise`);
ctx.addIf(`w${widgetID}.__owl__.isStarted`);
ctx.addLine(`def${defID} = w${widgetID}.updateProps(props${widgetID}, extra.forceUpdate);`);
ctx.addLine(`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`);
ctx.addElse();
ctx.addLine(`isNew${widgetID} = true`);
ctx.addIf(`props${widgetID} === w${widgetID}.__owl__.renderProps`);
@@ -2056,7 +2135,7 @@
ctx.closeIf();
ctx.addIf(`!def${defID}`);
ctx.addIf(`w${widgetID}`);
ctx.addLine(`def${defID} = w${widgetID}.updateProps(props${widgetID}, extra.forceUpdate);`);
ctx.addLine(`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`);
ctx.addElse();
ctx.addLine(`w${widgetID} = new context.widgets['${value}'](owner, props${widgetID});`);
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`);
@@ -2228,7 +2307,7 @@
}
if (didChange) {
this.__owl__.currentStoreProps = storeProps;
this.updateProps(ownProps, false);
this._updateProps(ownProps, false);
}
});
super.mounted();
@@ -2237,13 +2316,13 @@
this.env.store.off("update", this);
super.willUnmount();
}
updateProps(nextProps, forceUpdate) {
_updateProps(nextProps, forceUpdate) {
if (this.__owl__.ownProps !== nextProps) {
this.__owl__.currentStoreProps = mapStateToProps(this.env.store.state, nextProps);
}
this.__owl__.ownProps = nextProps;
const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps);
return super.updateProps(mergedProps, forceUpdate);
return super._updateProps(mergedProps, forceUpdate);
}
};
};
@@ -2263,19 +2342,6 @@
.replace(/"/g, "&#x27;")
.replace(/`/g, "&#x60;");
}
/**
* Remove trailing and leading spaces
*/
function htmlTrim(s) {
let result = s.replace(/(^\s+|\s+$)/g, "");
if (s[0] === " ") {
result = " " + result;
}
if (result !== " " && s[s.length - 1] === " ") {
result = result + " ";
}
return result;
}
function memoize(f, hash) {
if (!hash) {
hash = args => args.map(a => String(a)).join(",");
@@ -2317,24 +2383,6 @@
}
};
}
/**
* Find a node in a tree.
*
* This will traverse the tree (depth first) and return the first child that
* matches the predicate, if any
*/
function findInTree(tree, predicate) {
if (predicate(tree)) {
return tree;
}
for (let child of tree.children) {
let match = findInTree(child, predicate);
if (match) {
return match;
}
}
return null;
}
function patch$1(C, patchName, patch) {
const proto = C.prototype;
if (!proto.__patches) {
@@ -2422,10 +2470,8 @@
var _utils = /*#__PURE__*/Object.freeze({
escape: escape,
htmlTrim: htmlTrim,
memoize: memoize,
debounce: debounce,
findInTree: findInTree,
patch: patch$1,
unpatch: unpatch,
loadTemplates: loadTemplates,
@@ -2443,9 +2489,9 @@
exports.connect = connect;
exports.Store = Store;
exports._version = '0.8.0';
exports._date = '2019-04-26T13:32:23.079Z';
exports._hash = 'b44f274';
exports._version = '0.9.0';
exports._date = '2019-05-03T10:06:05.040Z';
exports._hash = '5b9abb6';
exports._url = 'https://github.com/odoo/owl';
}(this.owl = this.owl || {}));
-7
View File
@@ -1,7 +0,0 @@
{
"name": "owl-playground",
"description": "Odoo Web Library",
"scripts": {
"start": "python server.py"
}
}
+4 -4
View File
@@ -6,7 +6,7 @@ async function owlSourceCode() {
if (owlJS) {
return owlJS;
}
const result = await fetch("/libs/owl.js");
const result = await fetch("../owl.js");
owlJS = await result.text();
return owlJS;
}
@@ -196,7 +196,7 @@ class App extends owl.Component {
// inject js
const owlScript = doc.createElement("script");
owlScript.type = "text/javascript";
owlScript.src = "../libs/owl.js";
owlScript.src = "../owl.js";
owlScript.addEventListener("load", () => {
const script = doc.createElement("script");
script.type = "text/javascript";
@@ -278,8 +278,8 @@ class App extends owl.Component {
}
async downloadCode() {
await owl.utils.loadJS("../libs/FileSaver.min.js");
await owl.utils.loadJS("../libs/jszip.min.js");
await owl.utils.loadJS("libs/FileSaver.min.js");
await owl.utils.loadJS("libs/jszip.min.js");
const zip = new JSZip();
+2 -2
View File
@@ -5,8 +5,8 @@
<title>OWL Playground</title>
<link rel="icon" href="data:,">
<script src="../libs/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="../libs/owl.js"></script>
<script src="libs/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="../owl.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/solid.css" integrity="sha384-QokYePQSOwpBDuhlHOsX0ymF6R/vLk/UQVz3WHa6wygxI5oGTmDTv8wahFOSspdm" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/fontawesome.css" integrity="sha384-vd1e11sR28tEK9YANUtpIOdjGW14pS87bUBuOIoBILVWLFnS+MCX9T6MMf0VdPGq" crossorigin="anonymous">
+365 -296
View File
@@ -1,17 +1,17 @@
const CLICK_COUNTER = `class ClickCounter extends owl.Component {
constructor() {
super(...arguments);
this.template = "clickcounter";
this.state = {value: 0};
}
constructor() {
super(...arguments);
this.template = "clickcounter";
this.state = { value: 0 };
}
increment() {
this.state.value++;
}
increment() {
this.state.value++;
}
}
const qweb = new owl.QWeb(TEMPLATES);
const counter = new ClickCounter({qweb});
const counter = new ClickCounter({ qweb });
counter.mount(document.body);
`;
@@ -29,50 +29,50 @@ const CLICK_COUNTER_CSS = `button {
const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
class ClickCounter extends owl.Component {
template = "clickcounter";
state = {value: 0};
template = "clickcounter";
state = { value: 0 };
increment() {
this.state.value++;
}
increment() {
this.state.value++;
}
}
const qweb = new owl.QWeb(TEMPLATES);
const counter = new ClickCounter({qweb});
const counter = new ClickCounter({ qweb });
counter.mount(document.body);
`;
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
constructor(parent, props) {
super(parent, props);
this.template = "clickcounter";
this.state = {value: props.initialState || 0};
}
constructor(parent, props) {
super(parent, props);
this.template = "clickcounter";
this.state = { value: props.initialState || 0 };
}
increment() {
this.state.value++;
}
increment() {
this.state.value++;
}
}
let nextId = 1;
class App extends owl.Component {
constructor() {
super(...arguments);
this.template = "app";
this.state = {counters: []}
this.widgets = { ClickCounter };
}
constructor() {
super(...arguments);
this.template = "app";
this.state = { counters: [] }
this.widgets = { ClickCounter };
}
addCounter() {
this.state.counters.push(nextId++);
}
addCounter() {
this.state.counters.push(nextId++);
}
}
const qweb = new owl.QWeb(TEMPLATES);
const app = new App({qweb});
app.mount(document.body);`;
const app = new App({ qweb });
app.mount(document.body);
`;
const WIDGET_COMPOSITION_XML = `<templates>
<button t-name="clickcounter" t-on-click="increment">
@@ -95,54 +95,106 @@ const WIDGET_COMPOSITION_CSS = `button {
width: 220px;
}`;
const LIFECYCLE_DEMO = `const { Component, QWeb } = owl;
const ANIMATION = `// This example will not work if your browser does not support ESNext class fields
class App extends owl.Component {
template = "app";
state = {flag: 0};
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() {
this.state.n++;
}
toggle() {
this.state.flag = !this.state.flag;
}
}
class ParentWidget extends Component {
constructor() {
super(...arguments);
this.widgets = { HookWidget };
this.template = "demo.parentwidget";
this.state = { n: 0, flag: true };
}
increment() {
this.state.n++;
}
toggleSubWidget() {
this.state.flag = !this.state.flag;
}
const qweb = new owl.QWeb(TEMPLATES);
const app = new App({qweb});
app.mount(document.body);
`;
const ANIMATION_XML = `<templates>
<div t-name="app">
<button t-on-click="toggle">
Click Me!
</button>
<div>
<div t-if="state.flag" class="square" t-transition="fade">Hello</div>
</div>
</div>
</templates>
`;
const ANIMATION_CSS = `button {
width: 100px;
height: 30px;
font-size: 20px;
}
const qweb = new QWeb(TEMPLATES);
.square {
background-color: red;
width: 100px;
height: 100px;
color: white;
margin: 20px;
font-size: 24px;
line-height: 100px;
text-align: center;
line-height: 100px;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
`;
const LIFECYCLE_DEMO = `class HookWidget extends owl.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() {
this.state.n++;
}
}
class ParentWidget extends owl.Component {
constructor() {
super(...arguments);
this.widgets = { HookWidget };
this.template = "demo.parentwidget";
this.state = { n: 0, flag: true };
}
increment() {
this.state.n++;
}
toggleSubWidget() {
this.state.flag = !this.state.flag;
}
}
const qweb = new owl.QWeb(TEMPLATES);
const widget = new ParentWidget({ qweb });
widget.mount(document.body);
`;
@@ -164,92 +216,88 @@ const BENCHMARK_APP = `//-------------------------------------------------------
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"
"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];
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
});
messages.push({
id: i,
author: chooseRandomly(authors),
msg: \`\${i}: \${chooseRandomly(content)}\`,
likes: 0
});
}
//------------------------------------------------------------------------------
// Counter Widget
//------------------------------------------------------------------------------
class Counter extends owl.Component {
constructor(parent, props) {
super(parent, props);
this.template = "counter";
this.state = {
counter: props.initialState || 0
};
}
constructor(parent, props) {
super(parent, props);
this.template = "counter";
this.state = { counter: props.initialState || 0 };
}
increment(delta) {
this.state.counter += delta;
}
increment(delta) {
this.state.counter += delta;
}
}
//------------------------------------------------------------------------------
// Message Widget
//------------------------------------------------------------------------------
class Message extends owl.Component {
constructor() {
super(...arguments);
this.template = "message";
this.widgets = { Counter };
}
constructor() {
super(...arguments);
this.template = "message";
this.widgets = { Counter };
}
removeMessage() {
this.trigger("remove_message", {
id: this.props.id
});
}
removeMessage() {
this.trigger("remove_message", {
id: this.props.id
});
}
}
//------------------------------------------------------------------------------
// Root Widget
//------------------------------------------------------------------------------
class App extends owl.Component {
constructor() {
super(...arguments);
this.template = "root";
this.widgets = { Message };
this.state = {
messages: messages.slice(0, 10)
};
}
constructor() {
super(...arguments);
this.template = "root";
this.widgets = { Message };
this.state = { messages: messages.slice(0, 10) };
}
setMessageCount(n) {
this.state.messages = messages.slice(0,n);
}
setMessageCount(n) {
this.state.messages = messages.slice(0, n);
}
removeMessage(data) {
const index = messages.findIndex(m => m.id === data.id);
this.state.messages.splice(index, 1);
}
removeMessage(data) {
const index = messages.findIndex(m => m.id === data.id);
this.state.messages.splice(index, 1);
}
increment(delta) {
const n = this.state.messages.length + delta;
this.setMessageCount(n);
}
increment(delta) {
const n = this.state.messages.length + delta;
this.setMessageCount(n);
}
}
//------------------------------------------------------------------------------
// Application initialization
//------------------------------------------------------------------------------
const env = {
qweb: new owl.QWeb(TEMPLATES)
qweb: new owl.QWeb(TEMPLATES)
};
const app = new App(env);
@@ -349,177 +397,193 @@ const LOCALSTORAGE_KEY = "todos-odoo";
// Store Definition
//------------------------------------------------------------------------------
const actions = {
addTodo({ commit }, title) {
commit("addTodo", title);
},
removeTodo({ commit }, id) {
commit("removeTodo", id);
},
toggleTodo({ state, commit }, id) {
const todo = state.todos.find(t => t.id === id);
commit("editTodo", { id, completed: !todo.completed });
},
clearCompleted({ state, commit }) {
state.todos
.filter(todo => todo.completed)
.forEach(todo => {
commit("removeTodo", todo.id);
});
},
toggleAll({ state, commit }, completed) {
state.todos.forEach(todo => {
commit("editTodo", { id: todo.id, completed });
});
},
editTodo({ commit }, { id, title }) {
commit("editTodo", { id, title });
}
addTodo({ commit }, title) {
commit("addTodo", title);
},
removeTodo({ commit }, id) {
commit("removeTodo", id);
},
toggleTodo({ state, commit }, id) {
const todo = state.todos.find(t => t.id === id);
commit("editTodo", { id, completed: !todo.completed });
},
clearCompleted({ state, commit }) {
state.todos
.filter(todo => todo.completed)
.forEach(todo => {
commit("removeTodo", todo.id);
});
},
toggleAll({ state, commit }, completed) {
state.todos.forEach(todo => {
commit("editTodo", {
id: todo.id,
completed
});
});
},
editTodo({ commit }, { id, title }) {
commit("editTodo", { id, title });
}
};
const mutations = {
addTodo({ state }, title) {
const id = state.nextId++;
const todo = { id, title, completed: false };
state.todos.push(todo);
},
removeTodo({ state }, id) {
const index = state.todos.findIndex(t => t.id === id);
state.todos.splice(index, 1);
},
editTodo({ state }, { id, title, completed }) {
const todo = state.todos.find(t => t.id === id);
if (title !== undefined) {
todo.title = title;
addTodo({ state }, title) {
const id = state.nextId++;
const todo = {
id,
title,
completed: false
};
state.todos.push(todo);
},
removeTodo({ state }, id) {
const index = state.todos.findIndex(t => t.id === id);
state.todos.splice(index, 1);
},
editTodo({ state }, { id, title, completed }) {
const todo = state.todos.find(t => t.id === id);
if (title !== undefined) {
todo.title = title;
}
if (completed !== undefined) {
todo.completed = completed;
}
}
if (completed !== undefined) {
todo.completed = completed;
}
}
};
function makeStore() {
const todos = JSON.parse(
window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]"
);
const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1;
const state = { todos, nextId };
const store = new owl.Store({ state, actions, mutations });
store.on("update", null, () => {
const state = JSON.stringify(store.state.todos);
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
});
return store;
const todos = JSON.parse(
window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]"
);
const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1;
const state = {
todos,
nextId
};
const store = new owl.Store({
state,
actions,
mutations
});
store.on("update", null, () => {
const state = JSON.stringify(store.state.todos);
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
});
return store;
}
//------------------------------------------------------------------------------
// TodoItem
//------------------------------------------------------------------------------
class TodoItem extends owl.Component {
template = "todoitem";
state = { isEditing: false };
template = "todoitem";
state = { isEditing: false };
removeTodo() {
this.env.store.dispatch("removeTodo", this.props.id);
}
toggleTodo() {
this.env.store.dispatch("toggleTodo", this.props.id);
}
async editTodo() {
this.state.isEditing = true;
setTimeout(() => {
this.refs.input.value = "";
this.refs.input.focus();
this.refs.input.value = this.props.title;
});
}
handleKeyup(ev) {
if (ev.keyCode === ENTER_KEY) {
this.updateTitle(ev.target.value);
removeTodo() {
this.env.store.dispatch("removeTodo", this.props.id);
}
if (ev.keyCode === ESC_KEY) {
ev.target.value = this.props.title;
this.state.isEditing = false;
}
}
handleBlur(ev) {
this.updateTitle(ev.target.value);
}
updateTitle(title) {
const value = title.trim();
if (!value) {
this.removeTodo(this.props.id);
} else {
this.env.store.dispatch("editTodo", {
id: this.props.id,
title: value
});
this.state.isEditing = false;
toggleTodo() {
this.env.store.dispatch("toggleTodo", this.props.id);
}
async editTodo() {
this.state.isEditing = true;
setTimeout(() => {
this.refs.input.value = "";
this.refs.input.focus();
this.refs.input.value = this.props.title;
});
}
handleKeyup(ev) {
if (ev.keyCode === ENTER_KEY) {
this.updateTitle(ev.target.value);
}
if (ev.keyCode === ESC_KEY) {
ev.target.value = this.props.title;
this.state.isEditing = false;
}
}
handleBlur(ev) {
this.updateTitle(ev.target.value);
}
updateTitle(title) {
const value = title.trim();
if (!value) {
this.removeTodo(this.props.id);
} else {
this.env.store.dispatch("editTodo", {
id: this.props.id,
title: value
});
this.state.isEditing = false;
}
}
}
}
//------------------------------------------------------------------------------
// TodoApp
//------------------------------------------------------------------------------
function mapStateToProps(state) {
return { todos: state.todos };
return {
todos: state.todos
};
}
class TodoApp extends owl.Component {
template = "todoapp";
widgets = { TodoItem };
state = { filter: "all" };
template = "todoapp";
widgets = { TodoItem };
state = { filter: "all" };
get visibleTodos() {
let todos = this.props.todos;
if (this.state.filter === "active") {
todos = todos.filter(t => !t.completed);
get visibleTodos() {
let todos = this.props.todos;
if (this.state.filter === "active") {
todos = todos.filter(t => !t.completed);
}
if (this.state.filter === "completed") {
todos = todos.filter(t => t.completed);
}
return todos;
}
if (this.state.filter === "completed") {
todos = todos.filter(t => t.completed);
get allChecked() {
return this.props.todos.every(todo => todo.completed);
}
return todos;
}
get allChecked() {
return this.props.todos.every(todo => todo.completed);
}
get remaining() {
return this.props.todos.filter(todo => !todo.completed).length;
}
get remainingText() {
const items = this.remaining < 2 ? "item" : "items";
return \` \${items} left\`;
}
addTodo(ev) {
if (ev.keyCode === ENTER_KEY) {
const title = ev.target.value;
if (title.trim()) {
this.env.store.dispatch("addTodo", title);
}
ev.target.value = "";
get remaining() {
return this.props.todos.filter(todo => !todo.completed).length;
}
}
clearCompleted() {
this.env.store.dispatch("clearCompleted");
}
get remainingText() {
const items = this.remaining < 2 ? "item" : "items";
return \` \${items} left\`;
}
toggleAll() {
this.env.store.dispatch("toggleAll", !this.allChecked);
}
addTodo(ev) {
if (ev.keyCode === ENTER_KEY) {
const title = ev.target.value;
if (title.trim()) {
this.env.store.dispatch("addTodo", title);
}
ev.target.value = "";
}
}
setFilter(filter) {
this.state.filter = filter;
}
clearCompleted() {
this.env.store.dispatch("clearCompleted");
}
toggleAll() {
this.env.store.dispatch("toggleAll", !this.allChecked);
}
setFilter(filter) {
this.state.filter = filter;
}
}
const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
@@ -530,8 +594,8 @@ const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
const store = makeStore();
const qweb = new owl.QWeb(TEMPLATES);
const env = {
qweb,
store
qweb,
store
};
const app = new ConnectedTodoApp(env);
app.mount(document.body);
@@ -970,43 +1034,43 @@ html .clear-completed:active {
}
`;
const RESPONSIVE = `const { Component, QWeb, utils } = owl;
class SubWidget extends Component {
constructor() {
super(...arguments);
this.template = "subwidget";
}
const RESPONSIVE = `class SubWidget extends owl.Component {
constructor() {
super(...arguments);
this.template = "subwidget";
}
}
class ResponsiveWidget extends Component {
constructor() {
super(...arguments);
this.template = "responsivewidget";
this.widgets = { SubWidget };
}
class ResponsiveWidget extends owl.Component {
constructor() {
super(...arguments);
this.template = "responsivewidget";
this.widgets = { SubWidget };
}
}
function isMobile() {
return window.innerWidth <= 768;
return window.innerWidth <= 768;
}
const env = {
qweb: new QWeb(TEMPLATES),
isMobile: isMobile()
qweb: new owl.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)
"resize",
owl.utils.debounce(function() {
const _isMobile = isMobile();
if (_isMobile !== env.isMobile) {
widget.updateEnv({
isMobile: _isMobile
});
}
}, 20)
);
`;
@@ -1039,13 +1103,12 @@ const RESPONSIVE_CSS = `.info {
margin: 30px;
}`;
const EMPTY = `const {Component, QWeb} = owl;
class Widget extends Component {
const EMPTY = `class App extends owl.Component {
}
const qweb = new QWeb(TEMPLATES);
const widget = new Widget({qweb});
widget.mount(document.body);
const qweb = new owl.QWeb(TEMPLATES);
const app = new App({qweb});
app.mount(document.body);
`;
export const SAMPLES = [
@@ -1067,6 +1130,12 @@ export const SAMPLES = [
xml: WIDGET_COMPOSITION_XML,
css: WIDGET_COMPOSITION_CSS
},
{
description: "Animations",
code: ANIMATION,
xml: ANIMATION_XML,
css: ANIMATION_CSS,
},
{
description: "Lifecycle demo",
code: LIFECYCLE_DEMO,
-20
View File
@@ -1,20 +0,0 @@
import sys
import thread
import webbrowser
import time
import BaseHTTPServer, SimpleHTTPServer
def start_server():
httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 3600), SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd.serve_forever()
thread.start_new_thread(start_server,())
url = 'http://127.0.0.1:3600'
webbrowser.open_new(url)
while True:
try:
time.sleep(1)
except KeyboardInterrupt:
sys.exit(0)