[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 // 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) { async mount(target) {
const vnode = await this._prepare(); const vnode = await this._prepare();
if (this.__owl__.isDestroyed) { if (this.__owl__.isDestroyed) {
@@ -991,64 +974,94 @@
this._patch(vnode); this._patch(vnode);
target.appendChild(this.el); target.appendChild(this.el);
if (document.body.contains(target)) { if (document.body.contains(target)) {
this._visitSubTree(w => { this._callMounted();
if (!w.__owl__.isMounted && this.el.contains(w.el)) { }
w.__owl__.isMounted = true; }
w.mounted(); _callMounted() {
return true; const children = this.__owl__.children;
} for (let id in children) {
return false; 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() { unmount() {
if (this.el) { if (this.__owl__.isMounted) {
this._visitSubTree(w => { this._callWillUnmount();
if (w.__owl__.isMounted) {
w.willUnmount();
w.__owl__.isMounted = false;
return true;
}
return false;
});
this.el.remove(); this.el.remove();
} }
} }
async render(force = false) { async render(force = false, patchQueue) {
if (this.__owl__.isDestroyed) { if (this.__owl__.isDestroyed) {
return; return;
} }
const renderVDom = this._render(force); const shouldCallPatchHooks = !patchQueue;
if (shouldCallPatchHooks) {
patchQueue = [];
}
const renderVDom = this._render(force, patchQueue);
const renderId = this.__owl__.renderId; const renderId = this.__owl__.renderId;
const vnode = await renderVDom; const vnode = await renderVDom;
if (renderId === this.__owl__.renderId) { if (renderId === this.__owl__.renderId) {
// we only update the vnode and the actual DOM if no other rendering // we only update the vnode and the actual DOM if no other rendering
// occurred between now and when the render method was initially called. // 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); this._patch(vnode);
if (shouldCallPatchHooks) {
for (let i = patchQueue.length - 1; i >= 0; i--) {
const c = patchQueue[i];
c.patched(c.__owl__.willPatchVal);
}
}
} }
} }
destroy() { destroy() {
if (!this.__owl__.isDestroyed) { if (!this.__owl__.isDestroyed) {
for (let id in this.__owl__.children) { const el = this.el;
this.__owl__.children[id].destroy(); 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) { shouldUpdate(nextProps) {
return true; return true;
} }
@@ -1070,61 +1083,61 @@
await this.render(true); 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) { set(target, key, value) {
this.__owl__.observer.set(target, key, value); this.__owl__.observer.set(target, key, value);
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Private // Private
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
async _updateProps(nextProps) { async _updateProps(nextProps, forceUpdate = false, patchQueue) {
await this.willUpdateProps(nextProps); const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
this.props = nextProps; if (shouldUpdate) {
await this.render(); await this.willUpdateProps(nextProps);
this.props = nextProps;
await this.render(false, patchQueue);
}
} }
_patch(vnode) { _patch(vnode) {
this.__owl__.renderPromise = null; this.__owl__.renderPromise = null;
if (this.__owl__.vnode) { if (this.__owl__.vnode) {
const isMounted = this.__owl__.isMounted;
const snapshot = isMounted && this.willPatch();
this.__owl__.vnode = patch(this.__owl__.vnode, vnode); this.__owl__.vnode = patch(this.__owl__.vnode, vnode);
if (isMounted) {
this.patched(snapshot);
}
} }
else { else {
this.__owl__.vnode = patch(document.createElement(vnode.sel), vnode); this.__owl__.vnode = patch(document.createElement(vnode.sel), vnode);
} }
} }
async _prepare() { _prepare() {
this.__owl__.renderProps = this.props; this.__owl__.renderProps = this.props;
this.__owl__.renderPromise = this.willStart().then(() => { this.__owl__.renderPromise = this._prepareAndRender();
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();
});
return this.__owl__.renderPromise; 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++; this.__owl__.renderId++;
const promises = []; const promises = [];
const template = this.inlineTemplate || this.template;
if (this.__owl__.observer) { if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = false; this.__owl__.observer.allowMutations = false;
} }
let vnode = this.env.qweb.render(template, this, { let vnode = this.__owl__.render(this, {
promises, promises,
handlers: this.__owl__.boundHandlers, handlers: this.__owl__.boundHandlers,
forceUpdate: force forceUpdate: force,
patchQueue
}); });
if (this.__owl__.observer) { if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = true; this.__owl__.observer.allowMutations = true;
@@ -1144,32 +1157,17 @@
*/ */
_mount(vnode, elm) { _mount(vnode, elm) {
this.__owl__.vnode = patch(elm, vnode); 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; return this.__owl__.vnode;
} }
__mount() { __mount() {
if (this.__owl__.isMounted) { if (!this.__owl__.isMounted) {
return; this.__owl__.isMounted = true;
} this.mounted();
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);
}
} }
} }
_observeState() { _observeState() {
@@ -1200,6 +1198,62 @@
]; ];
const lineBreakRE = /[\r\n]/; const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g; 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 // Compilation Context
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -1335,26 +1389,9 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class QWeb { class QWeb {
constructor(data) { constructor(data) {
this.processedTemplates = {};
this.templates = {}; this.templates = {};
this.directives = []; this.directives = [];
this.utils = { this.utils = 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.directiveNames = { this.directiveNames = {
as: 1, as: 1,
name: 1, name: 1,
@@ -1364,7 +1401,8 @@
props: 1, props: 1,
key: 1, key: 1,
keepalive: 1, keepalive: 1,
debug: 1 debug: 1,
log: 1
}; };
[ [
forEachDirective, forEachDirective,
@@ -1377,6 +1415,9 @@
callDirective, callDirective,
onDirective, onDirective,
refDirective, refDirective,
transitionDirective,
debugDirective,
logDirective,
widgetDirective widgetDirective
].forEach(d => this.addDirective(d)); ].forEach(d => this.addDirective(d));
if (data) { if (data) {
@@ -1392,26 +1433,30 @@
* Add a template to the internal template map. Note that it is not * Add a template to the internal template map. Note that it is not
* immediately compiled. * immediately compiled.
*/ */
addTemplate(name, template, allowDuplicates = false) { addTemplate(name, xmlString, allowDuplicates = false) {
if (name in this.processedTemplates) { if (name in this.templates && allowDuplicates) {
if (allowDuplicates) { return;
return;
}
else {
throw new Error(`Template ${name} already defined`);
}
} }
const parser = new DOMParser(); const doc = parseXML(xmlString);
const doc = parser.parseFromString(template, "text/xml");
if (!doc.firstChild) { if (!doc.firstChild) {
throw new Error("Invalid template (should not be empty)"); throw new Error("Invalid template (should not be empty)");
} }
if (doc.getElementsByTagName("parsererror").length) { this._addTemplate(name, doc.firstChild);
throw new Error("Invalid XML in template"); }
_addTemplate(name, elem) {
if (name in this.templates) {
throw new Error(`Template ${name} already defined`);
} }
let elem = doc.firstChild;
this._processTemplate(elem); 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) { _processTemplate(elem) {
let tbranch = elem.querySelectorAll("[t-elif], [t-else]"); let tbranch = elem.querySelectorAll("[t-elif], [t-else]");
@@ -1453,19 +1498,14 @@
* the name given by the t-name attribute. * the name given by the t-name attribute.
*/ */
loadTemplates(xmlstr) { loadTemplates(xmlstr) {
const parser = new DOMParser(); const doc = parseXML(xmlstr);
const doc = parser.parseFromString(xmlstr, "text/xml");
if (doc.getElementsByTagName("parsererror").length) {
throw new Error("Invalid XML in template");
}
const templates = doc.getElementsByTagName("templates")[0]; const templates = doc.getElementsByTagName("templates")[0];
if (!templates) { if (!templates) {
return; return;
} }
for (let elem of templates.children) { for (let elem of templates.children) {
const name = elem.getAttribute("t-name"); const name = elem.getAttribute("t-name");
this._processTemplate(elem); this._addTemplate(name, elem);
this.processedTemplates[name] = elem;
} }
} }
/** /**
@@ -1474,20 +1514,16 @@
* @param {string} name the template should already have been added * @param {string} name the template should already have been added
*/ */
render(name, context = {}, extra = null) { 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`); throw new Error(`Template ${name} does not exist`);
} }
const template = this.templates[name] || this._compile(name); return template.fn.call(this, context, extra);
return template.call(this, context, extra);
} }
_compile(name) { _compile(name, elem) {
if (name in this.templates) { const isDebug = elem.attributes.hasOwnProperty("t-debug");
return this.templates[name];
}
const mainNode = this.processedTemplates[name];
const isDebug = mainNode.attributes.hasOwnProperty("t-debug");
const ctx = new Context(name); const ctx = new Context(name);
this._compileNode(mainNode, ctx); this._compileNode(elem, ctx);
if (ctx.shouldProtectContext) { if (ctx.shouldProtectContext) {
ctx.code.unshift(" context = Object.create(context);"); ctx.code.unshift(" context = Object.create(context);");
} }
@@ -1500,9 +1536,6 @@
throw new Error("A template should have one root node"); throw new Error("A template should have one root node");
} }
ctx.addLine(`return vn${ctx.rootNode};`); ctx.addLine(`return vn${ctx.rootNode};`);
if (isDebug) {
ctx.code.unshift(" debugger");
}
let template; let template;
try { try {
template = new Function("context", "extra", ctx.code.join("\n")); 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}`); throw new Error(`Invalid generated code while compiling template '${ctx.templateName.replace(/`/g, "'")}': ${e.message}`);
} }
if (isDebug) { 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; return template;
} }
/** /**
@@ -1890,7 +1923,7 @@
throw new Error("Invalid tag for t-call directive (should be 't')"); throw new Error("Invalid tag for t-call directive (should be 't')");
} }
const subTemplate = node.getAttribute("t-call"); const subTemplate = node.getAttribute("t-call");
const nodeTemplate = qweb.processedTemplates[subTemplate]; const nodeTemplate = qweb.templates[subTemplate];
if (!nodeTemplate) { if (!nodeTemplate) {
throw new Error(`Cannot find template "${subTemplate}" (t-call)`); throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
} }
@@ -1918,7 +1951,7 @@
.subContext("caller", nodeCopy) .subContext("caller", nodeCopy)
.subContext("variables", Object.create(vars)) .subContext("variables", Object.create(vars))
.subContext("definedVariables", Object.create(definedVariables)); .subContext("definedVariables", Object.create(definedVariables));
qweb._compileNode(nodeTemplate, subCtx); qweb._compileNode(nodeTemplate.elem, subCtx);
// close new scope // close new scope
if (hasNewVariables) { if (hasNewVariables) {
ctx.dedent(); ctx.dedent();
@@ -1952,7 +1985,19 @@
ctx.addLine(`context.${name} = _${keysID}[i];`); ctx.addLine(`context.${name} = _${keysID}[i];`);
ctx.addLine(`context.${name}_value = _${valuesID}[i];`); ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
const nodeCopy = node.cloneNode(true); 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}')`); console.warn(`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')`);
} }
nodeCopy.removeAttribute("t-foreach"); nodeCopy.removeAttribute("t-foreach");
@@ -1989,13 +2034,47 @@
const refDirective = { const refDirective = {
name: "ref", name: "ref",
priority: 95, priority: 95,
atNodeCreation({ ctx, node }) { atNodeCreation({ ctx, nodeID, value }) {
let ref = node.getAttribute("t-ref"); const refKey = `ref${ctx.generateID()}`;
ctx.addLine(`p${ctx.parentNode}.hook = { ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`);
create: (_, n) => context.refs[${ctx.formatExpression(ref)}] = n.elm, 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 = { const widgetDirective = {
name: "widget", name: "widget",
priority: 100, priority: 100,
@@ -2043,7 +2122,7 @@
// check if we can reuse current rendering promise // check if we can reuse current rendering promise
ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise`); ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise`);
ctx.addIf(`w${widgetID}.__owl__.isStarted`); 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.addElse();
ctx.addLine(`isNew${widgetID} = true`); ctx.addLine(`isNew${widgetID} = true`);
ctx.addIf(`props${widgetID} === w${widgetID}.__owl__.renderProps`); ctx.addIf(`props${widgetID} === w${widgetID}.__owl__.renderProps`);
@@ -2056,7 +2135,7 @@
ctx.closeIf(); ctx.closeIf();
ctx.addIf(`!def${defID}`); ctx.addIf(`!def${defID}`);
ctx.addIf(`w${widgetID}`); 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.addElse();
ctx.addLine(`w${widgetID} = new context.widgets['${value}'](owner, props${widgetID});`); ctx.addLine(`w${widgetID} = new context.widgets['${value}'](owner, props${widgetID});`);
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`); ctx.addLine(`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`);
@@ -2228,7 +2307,7 @@
} }
if (didChange) { if (didChange) {
this.__owl__.currentStoreProps = storeProps; this.__owl__.currentStoreProps = storeProps;
this.updateProps(ownProps, false); this._updateProps(ownProps, false);
} }
}); });
super.mounted(); super.mounted();
@@ -2237,13 +2316,13 @@
this.env.store.off("update", this); this.env.store.off("update", this);
super.willUnmount(); super.willUnmount();
} }
updateProps(nextProps, forceUpdate) { _updateProps(nextProps, forceUpdate) {
if (this.__owl__.ownProps !== nextProps) { if (this.__owl__.ownProps !== nextProps) {
this.__owl__.currentStoreProps = mapStateToProps(this.env.store.state, nextProps); this.__owl__.currentStoreProps = mapStateToProps(this.env.store.state, nextProps);
} }
this.__owl__.ownProps = nextProps; this.__owl__.ownProps = nextProps;
const mergedProps = Object.assign({}, nextProps, this.__owl__.currentStoreProps); 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, "&#x27;")
.replace(/`/g, "&#x60;"); .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) { function memoize(f, hash) {
if (!hash) { if (!hash) {
hash = args => args.map(a => String(a)).join(","); 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) { function patch$1(C, patchName, patch) {
const proto = C.prototype; const proto = C.prototype;
if (!proto.__patches) { if (!proto.__patches) {
@@ -2422,10 +2470,8 @@
var _utils = /*#__PURE__*/Object.freeze({ var _utils = /*#__PURE__*/Object.freeze({
escape: escape, escape: escape,
htmlTrim: htmlTrim,
memoize: memoize, memoize: memoize,
debounce: debounce, debounce: debounce,
findInTree: findInTree,
patch: patch$1, patch: patch$1,
unpatch: unpatch, unpatch: unpatch,
loadTemplates: loadTemplates, loadTemplates: loadTemplates,
@@ -2443,9 +2489,9 @@
exports.connect = connect; exports.connect = connect;
exports.Store = Store; exports.Store = Store;
exports._version = '0.8.0'; exports._version = '0.9.0';
exports._date = '2019-04-26T13:32:23.079Z'; exports._date = '2019-05-03T10:06:05.040Z';
exports._hash = 'b44f274'; exports._hash = '5b9abb6';
exports._url = 'https://github.com/odoo/owl'; exports._url = 'https://github.com/odoo/owl';
}(this.owl = this.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) { if (owlJS) {
return owlJS; return owlJS;
} }
const result = await fetch("/libs/owl.js"); const result = await fetch("../owl.js");
owlJS = await result.text(); owlJS = await result.text();
return owlJS; return owlJS;
} }
@@ -196,7 +196,7 @@ class App extends owl.Component {
// inject js // inject js
const owlScript = doc.createElement("script"); const owlScript = doc.createElement("script");
owlScript.type = "text/javascript"; owlScript.type = "text/javascript";
owlScript.src = "../libs/owl.js"; owlScript.src = "../owl.js";
owlScript.addEventListener("load", () => { owlScript.addEventListener("load", () => {
const script = doc.createElement("script"); const script = doc.createElement("script");
script.type = "text/javascript"; script.type = "text/javascript";
@@ -278,8 +278,8 @@ class App extends owl.Component {
} }
async downloadCode() { async downloadCode() {
await owl.utils.loadJS("../libs/FileSaver.min.js"); await owl.utils.loadJS("libs/FileSaver.min.js");
await owl.utils.loadJS("../libs/jszip.min.js"); await owl.utils.loadJS("libs/jszip.min.js");
const zip = new JSZip(); const zip = new JSZip();
+2 -2
View File
@@ -5,8 +5,8 @@
<title>OWL Playground</title> <title>OWL Playground</title>
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<script src="../libs/ace.js" type="text/javascript" charset="utf-8"></script> <script src="libs/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="../libs/owl.js"></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/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"> <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 { const CLICK_COUNTER = `class ClickCounter extends owl.Component {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.template = "clickcounter"; this.template = "clickcounter";
this.state = {value: 0}; this.state = { value: 0 };
} }
increment() { increment() {
this.state.value++; this.state.value++;
} }
} }
const qweb = new owl.QWeb(TEMPLATES); const qweb = new owl.QWeb(TEMPLATES);
const counter = new ClickCounter({qweb}); const counter = new ClickCounter({ qweb });
counter.mount(document.body); 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 const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
class ClickCounter extends owl.Component { class ClickCounter extends owl.Component {
template = "clickcounter"; template = "clickcounter";
state = {value: 0}; state = { value: 0 };
increment() { increment() {
this.state.value++; this.state.value++;
} }
} }
const qweb = new owl.QWeb(TEMPLATES); const qweb = new owl.QWeb(TEMPLATES);
const counter = new ClickCounter({qweb}); const counter = new ClickCounter({ qweb });
counter.mount(document.body); counter.mount(document.body);
`; `;
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component { const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
constructor(parent, props) { constructor(parent, props) {
super(parent, props); super(parent, props);
this.template = "clickcounter"; this.template = "clickcounter";
this.state = {value: props.initialState || 0}; this.state = { value: props.initialState || 0 };
} }
increment() { increment() {
this.state.value++; this.state.value++;
} }
} }
let nextId = 1; let nextId = 1;
class App extends owl.Component { class App extends owl.Component {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.template = "app"; this.template = "app";
this.state = {counters: []} this.state = { counters: [] }
this.widgets = { ClickCounter }; this.widgets = { ClickCounter };
} }
addCounter() { addCounter() {
this.state.counters.push(nextId++); this.state.counters.push(nextId++);
} }
} }
const qweb = new owl.QWeb(TEMPLATES); const qweb = new owl.QWeb(TEMPLATES);
const app = new App({qweb}); const app = new App({ qweb });
app.mount(document.body);`; app.mount(document.body);
`;
const WIDGET_COMPOSITION_XML = `<templates> const WIDGET_COMPOSITION_XML = `<templates>
<button t-name="clickcounter" t-on-click="increment"> <button t-name="clickcounter" t-on-click="increment">
@@ -95,54 +95,106 @@ const WIDGET_COMPOSITION_CSS = `button {
width: 220px; 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 { toggle() {
constructor() { this.state.flag = !this.state.flag;
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 Component { const qweb = new owl.QWeb(TEMPLATES);
constructor() { const app = new App({qweb});
super(...arguments); app.mount(document.body);
this.widgets = { HookWidget }; `;
this.template = "demo.parentwidget";
this.state = { n: 0, flag: true }; const ANIMATION_XML = `<templates>
} <div t-name="app">
increment() { <button t-on-click="toggle">
this.state.n++; Click Me!
} </button>
toggleSubWidget() { <div>
this.state.flag = !this.state.flag; <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 }); const widget = new ParentWidget({ qweb });
widget.mount(document.body); widget.mount(document.body);
`; `;
@@ -164,92 +216,88 @@ const BENCHMARK_APP = `//-------------------------------------------------------
const messages = []; const messages = [];
const authors = ["Aaron", "David", "Vincent"]; const authors = ["Aaron", "David", "Vincent"];
const content = [ const content = [
"Lorem ipsum dolor sit amet", "Lorem ipsum dolor sit amet",
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem",
"Excepteur sint occaecat cupidatat non proident" "Excepteur sint occaecat cupidatat non proident"
]; ];
function chooseRandomly(array) { function chooseRandomly(array) {
const index = Math.floor(Math.random() * array.length); const index = Math.floor(Math.random() * array.length);
return array[index]; return array[index];
} }
for (let i = 1; i < 16000; i++) { for (let i = 1; i < 16000; i++) {
messages.push({ messages.push({
id: i, id: i,
author: chooseRandomly(authors), author: chooseRandomly(authors),
msg: \`\${i}: \${chooseRandomly(content)}\`, msg: \`\${i}: \${chooseRandomly(content)}\`,
likes: 0 likes: 0
}); });
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Counter Widget // Counter Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class Counter extends owl.Component { class Counter extends owl.Component {
constructor(parent, props) { constructor(parent, props) {
super(parent, props); super(parent, props);
this.template = "counter"; this.template = "counter";
this.state = { this.state = { counter: props.initialState || 0 };
counter: props.initialState || 0 }
};
}
increment(delta) { increment(delta) {
this.state.counter += delta; this.state.counter += delta;
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Message Widget // Message Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class Message extends owl.Component { class Message extends owl.Component {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.template = "message"; this.template = "message";
this.widgets = { Counter }; this.widgets = { Counter };
} }
removeMessage() { removeMessage() {
this.trigger("remove_message", { this.trigger("remove_message", {
id: this.props.id id: this.props.id
}); });
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Root Widget // Root Widget
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class App extends owl.Component { class App extends owl.Component {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.template = "root"; this.template = "root";
this.widgets = { Message }; this.widgets = { Message };
this.state = { this.state = { messages: messages.slice(0, 10) };
messages: messages.slice(0, 10) }
};
}
setMessageCount(n) { setMessageCount(n) {
this.state.messages = messages.slice(0,n); this.state.messages = messages.slice(0, n);
} }
removeMessage(data) { removeMessage(data) {
const index = messages.findIndex(m => m.id === data.id); const index = messages.findIndex(m => m.id === data.id);
this.state.messages.splice(index, 1); this.state.messages.splice(index, 1);
} }
increment(delta) { increment(delta) {
const n = this.state.messages.length + delta; const n = this.state.messages.length + delta;
this.setMessageCount(n); this.setMessageCount(n);
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Application initialization // Application initialization
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const env = { const env = {
qweb: new owl.QWeb(TEMPLATES) qweb: new owl.QWeb(TEMPLATES)
}; };
const app = new App(env); const app = new App(env);
@@ -349,177 +397,193 @@ const LOCALSTORAGE_KEY = "todos-odoo";
// Store Definition // Store Definition
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const actions = { const actions = {
addTodo({ commit }, title) { addTodo({ commit }, title) {
commit("addTodo", title); commit("addTodo", title);
}, },
removeTodo({ commit }, id) { removeTodo({ commit }, id) {
commit("removeTodo", id); commit("removeTodo", id);
}, },
toggleTodo({ state, commit }, id) { toggleTodo({ state, commit }, id) {
const todo = state.todos.find(t => t.id === id); const todo = state.todos.find(t => t.id === id);
commit("editTodo", { id, completed: !todo.completed }); commit("editTodo", { id, completed: !todo.completed });
}, },
clearCompleted({ state, commit }) { clearCompleted({ state, commit }) {
state.todos state.todos
.filter(todo => todo.completed) .filter(todo => todo.completed)
.forEach(todo => { .forEach(todo => {
commit("removeTodo", todo.id); commit("removeTodo", todo.id);
}); });
}, },
toggleAll({ state, commit }, completed) { toggleAll({ state, commit }, completed) {
state.todos.forEach(todo => { state.todos.forEach(todo => {
commit("editTodo", { id: todo.id, completed }); commit("editTodo", {
}); id: todo.id,
}, completed
editTodo({ commit }, { id, title }) { });
commit("editTodo", { id, title }); });
} },
editTodo({ commit }, { id, title }) {
commit("editTodo", { id, title });
}
}; };
const mutations = { const mutations = {
addTodo({ state }, title) { addTodo({ state }, title) {
const id = state.nextId++; const id = state.nextId++;
const todo = { id, title, completed: false }; const todo = {
state.todos.push(todo); id,
}, title,
removeTodo({ state }, id) { completed: false
const index = state.todos.findIndex(t => t.id === id); };
state.todos.splice(index, 1); state.todos.push(todo);
}, },
editTodo({ state }, { id, title, completed }) { removeTodo({ state }, id) {
const todo = state.todos.find(t => t.id === id); const index = state.todos.findIndex(t => t.id === id);
if (title !== undefined) { state.todos.splice(index, 1);
todo.title = title; },
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() { function makeStore() {
const todos = JSON.parse( const todos = JSON.parse(
window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]" window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]"
); );
const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1; const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1;
const state = { todos, nextId }; const state = {
const store = new owl.Store({ state, actions, mutations }); todos,
store.on("update", null, () => { nextId
const state = JSON.stringify(store.state.todos); };
window.localStorage.setItem(LOCALSTORAGE_KEY, state); const store = new owl.Store({
}); state,
return store; actions,
mutations
});
store.on("update", null, () => {
const state = JSON.stringify(store.state.todos);
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
});
return store;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// TodoItem // TodoItem
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class TodoItem extends owl.Component { class TodoItem extends owl.Component {
template = "todoitem"; template = "todoitem";
state = { isEditing: false }; state = { isEditing: false };
removeTodo() { removeTodo() {
this.env.store.dispatch("removeTodo", this.props.id); 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);
} }
if (ev.keyCode === ESC_KEY) {
ev.target.value = this.props.title;
this.state.isEditing = false;
}
}
handleBlur(ev) { toggleTodo() {
this.updateTitle(ev.target.value); this.env.store.dispatch("toggleTodo", this.props.id);
} }
updateTitle(title) { async editTodo() {
const value = title.trim(); this.state.isEditing = true;
if (!value) { setTimeout(() => {
this.removeTodo(this.props.id); this.refs.input.value = "";
} else { this.refs.input.focus();
this.env.store.dispatch("editTodo", { this.refs.input.value = this.props.title;
id: this.props.id, });
title: value }
});
this.state.isEditing = false; 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 // TodoApp
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
function mapStateToProps(state) { function mapStateToProps(state) {
return { todos: state.todos }; return {
todos: state.todos
};
} }
class TodoApp extends owl.Component { class TodoApp extends owl.Component {
template = "todoapp"; template = "todoapp";
widgets = { TodoItem }; widgets = { TodoItem };
state = { filter: "all" }; state = { filter: "all" };
get visibleTodos() { get visibleTodos() {
let todos = this.props.todos; let todos = this.props.todos;
if (this.state.filter === "active") { if (this.state.filter === "active") {
todos = todos.filter(t => !t.completed); 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() { get remaining() {
return this.props.todos.every(todo => todo.completed); return this.props.todos.filter(todo => !todo.completed).length;
}
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 = "";
} }
}
clearCompleted() { get remainingText() {
this.env.store.dispatch("clearCompleted"); const items = this.remaining < 2 ? "item" : "items";
} return \` \${items} left\`;
}
toggleAll() { addTodo(ev) {
this.env.store.dispatch("toggleAll", !this.allChecked); if (ev.keyCode === ENTER_KEY) {
} const title = ev.target.value;
if (title.trim()) {
this.env.store.dispatch("addTodo", title);
}
ev.target.value = "";
}
}
setFilter(filter) { clearCompleted() {
this.state.filter = filter; 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); const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
@@ -530,8 +594,8 @@ const ConnectedTodoApp = owl.connect(mapStateToProps)(TodoApp);
const store = makeStore(); const store = makeStore();
const qweb = new owl.QWeb(TEMPLATES); const qweb = new owl.QWeb(TEMPLATES);
const env = { const env = {
qweb, qweb,
store store
}; };
const app = new ConnectedTodoApp(env); const app = new ConnectedTodoApp(env);
app.mount(document.body); app.mount(document.body);
@@ -970,43 +1034,43 @@ html .clear-completed:active {
} }
`; `;
const RESPONSIVE = `const { Component, QWeb, utils } = owl; const RESPONSIVE = `class SubWidget extends owl.Component {
constructor() {
class SubWidget extends Component { super(...arguments);
constructor() { this.template = "subwidget";
super(...arguments); }
this.template = "subwidget";
}
} }
class ResponsiveWidget extends Component { class ResponsiveWidget extends owl.Component {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.template = "responsivewidget"; this.template = "responsivewidget";
this.widgets = { SubWidget }; this.widgets = { SubWidget };
} }
} }
function isMobile() { function isMobile() {
return window.innerWidth <= 768; return window.innerWidth <= 768;
} }
const env = { const env = {
qweb: new QWeb(TEMPLATES), qweb: new owl.QWeb(TEMPLATES),
isMobile: isMobile() isMobile: isMobile()
}; };
const widget = new ResponsiveWidget(env); const widget = new ResponsiveWidget(env);
widget.mount(document.body); widget.mount(document.body);
window.addEventListener( window.addEventListener(
"resize", "resize",
utils.debounce(function() { owl.utils.debounce(function() {
const _isMobile = isMobile(); const _isMobile = isMobile();
if (_isMobile !== env.isMobile) { if (_isMobile !== env.isMobile) {
widget.updateEnv({ isMobile: _isMobile }); widget.updateEnv({
} isMobile: _isMobile
}, 20) });
}
}, 20)
); );
`; `;
@@ -1039,13 +1103,12 @@ const RESPONSIVE_CSS = `.info {
margin: 30px; margin: 30px;
}`; }`;
const EMPTY = `const {Component, QWeb} = owl; const EMPTY = `class App extends owl.Component {
class Widget extends Component {
} }
const qweb = new QWeb(TEMPLATES); const qweb = new owl.QWeb(TEMPLATES);
const widget = new Widget({qweb}); const app = new App({qweb});
widget.mount(document.body); app.mount(document.body);
`; `;
export const SAMPLES = [ export const SAMPLES = [
@@ -1067,6 +1130,12 @@ export const SAMPLES = [
xml: WIDGET_COMPOSITION_XML, xml: WIDGET_COMPOSITION_XML,
css: WIDGET_COMPOSITION_CSS css: WIDGET_COMPOSITION_CSS
}, },
{
description: "Animations",
code: ANIMATION,
xml: ANIMATION_XML,
css: ANIMATION_CSS,
},
{ {
description: "Lifecycle demo", description: "Lifecycle demo",
code: 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)