From 62469c42643a6beb270a757926703da462b97036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 3 May 2019 12:28:59 +0200 Subject: [PATCH] [IMP] playground: update Owl to 0.9.0 --- libs/owl.js => owl.js | 462 ++++++------ package.json | 7 - playground/app.js | 8 +- playground/index.html | 4 +- {libs => playground/libs}/FileSaver.min.js | 0 {libs => playground/libs}/ace.js | 0 {libs => playground/libs}/jszip.min.js | 0 {libs => playground/libs}/mode-css.js | 0 {libs => playground/libs}/mode-javascript.js | 0 {libs => playground/libs}/mode-xml.js | 0 {libs => playground/libs}/theme-monokai.js | 0 .../libs}/worker-javascript.js | 0 playground/samples.js | 661 ++++++++++-------- server.py | 20 - 14 files changed, 625 insertions(+), 537 deletions(-) rename libs/owl.js => owl.js (90%) delete mode 100644 package.json rename {libs => playground/libs}/FileSaver.min.js (100%) rename {libs => playground/libs}/ace.js (100%) rename {libs => playground/libs}/jszip.min.js (100%) rename {libs => playground/libs}/mode-css.js (100%) rename {libs => playground/libs}/mode-javascript.js (100%) rename {libs => playground/libs}/mode-xml.js (100%) rename {libs => playground/libs}/theme-monokai.js (100%) rename {libs => playground/libs}/worker-javascript.js (100%) delete mode 100644 server.py diff --git a/libs/owl.js b/owl.js similarity index 90% rename from libs/owl.js rename to owl.js index a750e83c..352f6a3b 100644 --- a/libs/owl.js +++ b/owl.js @@ -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, "'") .replace(/`/g, "`"); } - /** - * 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 || {})); diff --git a/package.json b/package.json deleted file mode 100644 index 02ad6ca1..00000000 --- a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "owl-playground", - "description": "Odoo Web Library", - "scripts": { - "start": "python server.py" - } -} diff --git a/playground/app.js b/playground/app.js index 4802fba1..434efce6 100644 --- a/playground/app.js +++ b/playground/app.js @@ -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(); diff --git a/playground/index.html b/playground/index.html index 1009a642..4fae2f13 100644 --- a/playground/index.html +++ b/playground/index.html @@ -5,8 +5,8 @@ OWL Playground - - + + diff --git a/libs/FileSaver.min.js b/playground/libs/FileSaver.min.js similarity index 100% rename from libs/FileSaver.min.js rename to playground/libs/FileSaver.min.js diff --git a/libs/ace.js b/playground/libs/ace.js similarity index 100% rename from libs/ace.js rename to playground/libs/ace.js diff --git a/libs/jszip.min.js b/playground/libs/jszip.min.js similarity index 100% rename from libs/jszip.min.js rename to playground/libs/jszip.min.js diff --git a/libs/mode-css.js b/playground/libs/mode-css.js similarity index 100% rename from libs/mode-css.js rename to playground/libs/mode-css.js diff --git a/libs/mode-javascript.js b/playground/libs/mode-javascript.js similarity index 100% rename from libs/mode-javascript.js rename to playground/libs/mode-javascript.js diff --git a/libs/mode-xml.js b/playground/libs/mode-xml.js similarity index 100% rename from libs/mode-xml.js rename to playground/libs/mode-xml.js diff --git a/libs/theme-monokai.js b/playground/libs/theme-monokai.js similarity index 100% rename from libs/theme-monokai.js rename to playground/libs/theme-monokai.js diff --git a/libs/worker-javascript.js b/playground/libs/worker-javascript.js similarity index 100% rename from libs/worker-javascript.js rename to playground/libs/worker-javascript.js diff --git a/playground/samples.js b/playground/samples.js index e55def1c..49c2b777 100644 --- a/playground/samples.js +++ b/playground/samples.js @@ -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 = ` +
+
Hello
+
+ +
+`; + +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, diff --git a/server.py b/server.py deleted file mode 100644 index 8f6ab3ce..00000000 --- a/server.py +++ /dev/null @@ -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) \ No newline at end of file