diff --git a/owl.js b/owl.js index 04588d8c..da56d4a6 100644 --- a/owl.js +++ b/owl.js @@ -379,7 +379,6 @@ //------------------------------------------------------------------------------ class CompilationContext { constructor(name) { - this.nextID = 1; this.code = []; this.variables = {}; this.escaping = false; @@ -393,7 +392,6 @@ this.shouldDefineUtils = false; this.shouldDefineRefs = false; this.shouldDefineResult = true; - this.shouldDefineSibling = true; this.shouldProtectContext = false; this.shouldTrackScope = false; this.loopNumber = 0; @@ -402,16 +400,16 @@ this.hasParentWidget = false; this.scopeVars = []; this.currentKey = ""; - this.lastNodeKey = ""; // temp variable to communicate to previous caller this.templates = {}; + this.callingLevel = 0; + this.inliningLevel = 0; this.rootContext = this; this.templateName = name || "noname"; this.templates[this.templateName] = true; this.addLine("var h = this.h;"); } generateID() { - const id = this.rootContext.nextID++; - return id; + return CompilationContext.nextID++; } /** * This method generates a "template key", which is basically a unique key @@ -427,8 +425,8 @@ for (let i = 0; i < this.loopNumber - 1; i++) { locationExpr += `\${i${i + 1}}__`; } - if (this.lastNodeKey || this.currentKey) { - const k = this.lastNodeKey || this.currentKey; + if (this.currentKey) { + const k = this.currentKey; this.addLine(`let k${id} = ${locationExpr}\` + ${k};`); } else { @@ -454,9 +452,6 @@ if (this.shouldDefineResult) { this.code.unshift(" let result;"); } - if (this.shouldDefineSibling) { - this.code.unshift(" let sibling = null;"); - } if (this.shouldDefineRefs) { this.code.unshift(" context.__owl__.refs = context.__owl__.refs || {};"); } @@ -498,6 +493,10 @@ subContext(key, value) { const newContext = Object.create(this); newContext[key] = value; + if (key === "caller") { + newContext.callingLevel++; + newContext.inliningLevel++; + } return newContext; } indent() { @@ -528,6 +527,27 @@ this.dedent(); this.addLine("}"); } + /** + * Recursively (inverse) fetches the `caller` of a context + * Useful to determine to which t-call a t-raw="0" refers + */ + getCaller(targetLevel) { + if (targetLevel === undefined) { + targetLevel = this.inliningLevel; + } + if (targetLevel === this.callingLevel) { + return this.caller || null; + } + const proto = this.__proto__; + return proto ? proto.getCaller(targetLevel) : null; + } + /** + * Marks the context with the current recursive level + * in which we are for inlining archs (t-raw="0") + */ + getInliningContext() { + return this.subContext("inliningLevel", this.inliningLevel - 1); + } getValue(val) { return val in this.variables ? this.getValue(this.variables[val]) : val; } @@ -557,6 +577,7 @@ return "`" + r + "`"; } } + CompilationContext.nextID = 1; //------------------------------------------------------------------------------ // module/props.ts @@ -853,10 +874,11 @@ vnode.elm = api.createComment(vnode.text); } else if (sel !== undefined) { - const elm = (vnode.elm = - isDef(data) && isDef((i = data.ns)) - ? api.createElementNS(i, sel) - : api.createElement(sel)); + const elm = vnode.elm || + (vnode.elm = + isDef(data) && isDef((i = data.ns)) + ? api.createElementNS(i, sel) + : api.createElement(sel)); for (i = 0, iLen = cbs.create.length; i < iLen; ++i) cbs.create[i](emptyNode, vnode); if (array(children)) { @@ -1548,8 +1570,7 @@ if (parentContext) { ctx.templates = Object.create(parentContext.templates); ctx.variables = Object.create(parentContext.variables); - ctx.nextID = parentContext.nextID + 1; - ctx.parentNode = parentContext.parentNode || ctx.nextID++; + ctx.parentNode = parentContext.parentNode || ctx.generateID(); ctx.allowMultipleRoots = true; ctx.hasParentWidget = true; ctx.shouldDefineResult = false; @@ -1638,13 +1659,16 @@ } return; } + if (ctx !== ctx.rootContext) { + ctx = ctx.subContext("currentKey", ctx.currentKey); + } const firstLetter = node.tagName[0]; if (firstLetter === firstLetter.toUpperCase()) { // this is a component, we modify in place the xml document to change // to node.setAttribute("t-component", node.tagName); } - else if (node.tagName !== 't' && node.hasAttribute('t-component')) { + else if (node.tagName !== "t" && node.hasAttribute("t-component")) { throw new Error(`Directive 't-component' can only be used on nodes (used on a <${node.tagName}>)`); } const attributes = node.attributes; @@ -1714,7 +1738,6 @@ if (node.nodeName !== "t") { let nodeID = this._compileGenericNode(node, ctx, withHandlers); ctx = ctx.withParent(nodeID); - ctx = ctx.subContext("currentKey", ctx.lastNodeKey); let nodeHooks = {}; let addNodeHook = function (hook, handler) { nodeHooks[hook] = nodeHooks[hook] || []; @@ -1884,7 +1907,7 @@ } } let nodeID = ctx.generateID(); - let nodeKey = ctx.lastNodeKey || nodeID; + let nodeKey = ctx.currentKey || nodeID; const parts = [`key:${nodeKey}`]; if (attrs.length + tattrs.length > 0) { parts.push(`attrs:{${attrs.join(",")}}`); @@ -1989,9 +2012,12 @@ }; QWeb.utils.htmlToVDOM = htmlToVDOM; function compileValueNode(value, node, qweb, ctx) { - if (value === "0" && ctx.caller) { - qweb._compileNode(ctx.caller, ctx); - return; + if (value === "0") { + const caller = ctx.getCaller(); + if (caller) { + qweb._compileNode(caller, ctx.getInliningContext()); + return; + } } if (value.xml instanceof NodeList && !value.id) { for (let node of Array.from(value.xml)) { @@ -2148,11 +2174,9 @@ nodeCopy.removeAttribute("t-call"); // extract variables from nodecopy const tempCtx = new CompilationContext(); - tempCtx.nextID = ctx.rootContext.nextID; tempCtx.allowMultipleRoots = true; qweb._compileNode(nodeCopy, tempCtx); const vars = Object.assign({}, ctx.variables, tempCtx.variables); - ctx.rootContext.nextID = tempCtx.nextID; const templateMap = Object.create(ctx.templates); // open new scope, if necessary const hasNewVariables = Object.keys(tempCtx.variables).length > 0; @@ -2378,6 +2402,8 @@ } elm.classList.add(name + "-enter"); elm.classList.add(name + "-enter-active"); + elm.classList.remove(name + "-leave-active"); + elm.classList.remove(name + "-leave-to"); const finalize = () => { elm.classList.remove(name + "-enter-active"); elm.classList.remove(name + "-enter-to"); @@ -2394,6 +2420,9 @@ elm.classList.add(name + "-leave"); elm.classList.add(name + "-leave-active"); const finalize = () => { + if (!elm.classList.contains(name + "-leave-active")) { + return; + } elm.classList.remove(name + "-leave-active"); elm.classList.remove(name + "-leave-to"); rm(); @@ -2462,11 +2491,11 @@ if (!ctx.parentNode) { ctx.rootContext.shouldDefineResult = true; ctx.rootContext.shouldDefineUtils = true; - parentNode = `children${ctx.nextID++}`; + parentNode = `children${ctx.generateID()}`; ctx.addLine(`let ${parentNode}= []`); ctx.addLine(`result = {}`); } - ctx.addLine(`slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: ${parentNode}, vars: extra.vars, parent: owner}));`); + ctx.addLine(`slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: ${parentNode}, vars: extra.vars, parent: extra.parent || owner}));`); if (!ctx.parentNode) { ctx.addLine(`utils.defineProxy(result, ${parentNode}[0]);`); } @@ -2536,7 +2565,7 @@ atNodeEncounter({ ctx, value }) { let id = ctx.generateID(); ctx.addLine(`const nodeKey${id} = ${ctx.formatExpression(value)};`); - ctx.lastNodeKey = `nodeKey${id}`; + ctx.currentKey = `nodeKey${id}`; } }); @@ -2801,10 +2830,6 @@ ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`); refExpr = `context.__owl__.refs[${refKey}] = w${componentID};`; } - let transitionsInsertCode = ""; - if (transition) { - transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`; - } let finalizeComponentCode = `w${componentID}.destroy();`; if (ref) { finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`; @@ -2813,6 +2838,7 @@ finalizeComponentCode = `let finalize = () => { ${finalizeComponentCode} }; + delete w${componentID}.__owl__.transitionInserted; utils.transitionRemove(vn, '${transition}', finalize);`; } let createHook = ""; @@ -2935,7 +2961,7 @@ styleCode = `.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};w${componentID}.el.style=${tattStyle};});`; } ctx.addLine(`w${componentID}.__updateProps(props${componentID}, extra.fiber${scopeVars && - ", " + scopeVars}, sibling)${styleCode};`); + ", " + scopeVars})${styleCode};`); ctx.addLine(`let pvnode = w${componentID}.__owl__.pvnode;`); if (registerCode) { ctx.addLine(registerCode); @@ -2955,6 +2981,10 @@ // maybe only do this in dev mode... ctx.addLine(`if (!W${componentID}) {throw new Error('Cannot find the definition of component "' + componentKey${componentID} + '"')}`); ctx.addLine(`w${componentID} = new W${componentID}(parent, props${componentID});`); + if (transition) { + ctx.addLine(`const __patch${componentID} = w${componentID}.__patch;`); + ctx.addLine(`w${componentID}.__patch = fiber => {__patch${componentID}.call(w${componentID}, fiber); if(!w${componentID}.__owl__.transitionInserted){w${componentID}.__owl__.transitionInserted = true;utils.transitionInsert(w${componentID}.__owl__.vnode, '${transition}');}};`); + } ctx.addLine(`parent.__owl__.cmap[${templateKey}] = w${componentID}.__owl__.id;`); if (hasSlots) { const clone = node.cloneNode(true); @@ -2980,9 +3010,10 @@ QWeb.slots[`${slotId}_default`] = slotFn; } } - ctx.addLine(`let def${defID} = w${componentID}.__prepare(extra.fiber, ${scopeVars}, sibling);`); + ctx.addLine(`let def${defID} = w${componentID}.__prepare(extra.fiber, ${scopeVars});`); // hack: specify empty remove hook to prevent the node from being removed from the DOM - ctx.addLine(`let pvnode = h('dummy', {key: ${templateKey}, hook: {insert(vn) { let nvn=w${componentID}.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});`); + const insertHook = refExpr ? `insert(vn) {${refExpr}},` : ""; + ctx.addLine(`let pvnode = h('dummy', {key: ${templateKey}, hook: {${insertHook}remove() {},destroy(vn) {${finalizeComponentCode}}}});`); ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`); ctx.addLine(`def${defID}.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});`); if (registerCode) { @@ -2997,7 +3028,6 @@ ctx.addLine(`w${componentID}.__owl__.classObj=${classObj};`); } ctx.addLine(`w${componentID}.__owl__.parentLastFiberId = extra.fiber.id;`); - ctx.addLine(`sibling = w${componentID}.__owl__.currentFiber || sibling;`); return true; } }); @@ -3009,6 +3039,9 @@ this.requestAnimationFrame = requestAnimationFrame; } addFiber(fiber) { + // if the fiber was remapped into a larger rendering fiber, it may not be a + // root fiber. But we only want to register root fibers + fiber = fiber.root; return new Promise((resolve, reject) => { if (fiber.error) { return reject(fiber.error); @@ -3084,7 +3117,7 @@ * states and in general determine the state of the rendering. */ class Fiber { - constructor(parent, component, scope, vars, force, target) { + constructor(parent, component, force, target) { this.id = Fiber.nextId++; // isCompleted means that the rendering corresponding to this fiber's work is // done, either because the component has been mounted or patched, or because @@ -3108,15 +3141,17 @@ this.vnode = null; this.child = null; this.sibling = null; + this.lastChild = null; this.parent = null; - this.force = force; - this.scope = scope; - this.vars = vars; this.component = component; + this.force = force; this.target = target; + const __owl__ = component.__owl__; + this.scope = __owl__.scope; + this.vars = __owl__.vars; this.root = parent ? parent.root : this; this.parent = parent; - let oldFiber = component.__owl__.currentFiber; + let oldFiber = __owl__.currentFiber; if (oldFiber && !oldFiber.isCompleted) { if (oldFiber.root === oldFiber && !parent) { // both oldFiber and this fiber are root fibers @@ -3128,7 +3163,7 @@ } } this.root.counter++; - component.__owl__.currentFiber = this; + __owl__.currentFiber = this; } /** * When the oldFiber is not completed yet, and both oldFiber and this fiber @@ -3146,6 +3181,7 @@ // remove relation to children oldFiber.child.parent = null; oldFiber.child = null; + oldFiber.lastChild = null; } oldFiber.counter = 1; // re-initialize counter oldFiber.id = Fiber.nextId++; @@ -3158,6 +3194,7 @@ */ _remapFiber(oldFiber) { oldFiber.cancel(); + this.shouldPatch = oldFiber.shouldPatch; if (oldFiber === oldFiber.root) { oldFiber.counter++; } @@ -3166,6 +3203,9 @@ this.parent = oldFiber.parent; this.root = this.parent.root; this.sibling = oldFiber.sibling; + if (this.parent.lastChild === oldFiber) { + this.parent.lastChild = this; + } if (this.parent.child === oldFiber) { this.parent.child = this; } @@ -3212,54 +3252,58 @@ * are ready, and the scheduler decides to process it. */ complete() { - const component = this.component; - if (this.target) { - component.__patch(this.vnode); - this.target.appendChild(component.el); - if (document.body.contains(this.target)) { - component.__callMounted(); - } - } - else if (component.__owl__.isMounted && this === this.root) { - this.patchComponents(); - } + let component = this.component; this.isCompleted = true; - } - /** - * Compute and apply the patch queue of the fiber. - * 1) Call 'willPatch' on the component of each patch - * 2) Call '__patch' on the component of each patch - * 3) Call 'patched' on the component of each patch, in reverse order - */ - patchComponents() { + if (!this.target && !component.__owl__.isMounted) { + return; + } + // build patchQueue const patchQueue = []; const doWork = function (f) { - if (f.shouldPatch) { - patchQueue.push(f); - return f.child; - } + patchQueue.push(f); + return f.child; }; this._walk(doWork); - let component = this.component; const patchLen = patchQueue.length; + // call willPatch hook on each fiber of patchQueue for (let i = 0; i < patchLen; i++) { - component = patchQueue[i].component; - if (component.__owl__.willPatchCB) { - component.__owl__.willPatchCB(); + const fiber = patchQueue[i]; + if (fiber.shouldPatch) { + component = fiber.component; + if (component.__owl__.willPatchCB) { + component.__owl__.willPatchCB(); + } + component.willPatch(); } - component.willPatch(); } - for (let i = 0; i < patchLen; i++) { + // call __patch on each fiber of (reversed) patchQueue + for (let i = patchLen - 1; i >= 0; i--) { const fiber = patchQueue[i]; component = fiber.component; component.__patch(fiber.vnode); + if (!fiber.shouldPatch && (!fiber.target || i !== 0)) { + component.__owl__.pvnode.elm = component.__owl__.vnode.elm; + } component.__owl__.currentFiber = null; } + // insert into the DOM (mount case) + let inDOM = false; + if (this.target) { + this.target.appendChild(this.component.el); + inDOM = document.body.contains(this.target); + } + // call patched/mounted hook on each fiber of (reversed) patchQueue for (let i = patchLen - 1; i >= 0; i--) { - component = patchQueue[i].component; - component.patched(); - if (component.__owl__.patchedCB) { - component.__owl__.patchedCB(); + const fiber = patchQueue[i]; + component = fiber.component; + if (fiber.shouldPatch && !this.target) { + component.patched(); + if (component.__owl__.patchedCB) { + component.__owl__.patchedCB(); + } + } + else if (this.target ? inDOM : true) { + component.__callMounted(); } } } @@ -3358,7 +3402,7 @@ throw e; } if (!isValid) { - throw new Error(`Props '${propName}' of invalid type in component '${Widget.name}'`); + throw new Error(`Invalid Prop '${propName}' in component '${Widget.name}'`); } } for (let propName in props) { @@ -3399,7 +3443,10 @@ if (propDef.optional && prop === undefined) { return true; } - let result = isValidProp(prop, propDef.type); + let result = propDef.type ? isValidProp(prop, propDef.type) : true; + if (propDef.validate) { + result = result && propDef.validate(prop); + } if (propDef.type === Array && propDef.element) { for (let i = 0, iLen = prop.length; i < iLen; i++) { result = result && isValidProp(prop[i], propDef.element); @@ -3501,7 +3548,9 @@ observer: null, renderFn: qweb.render.bind(qweb, template), classObj: null, - refs: null + refs: null, + scope: null, + vars: null }; } /** @@ -3593,12 +3642,13 @@ if (__owl__.isMounted) { return Promise.resolve(); } - if (!(target instanceof HTMLElement)) { + if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) { let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`; message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`; throw new Error(message); } - const fiber = new Fiber(null, this, undefined, undefined, false, target); + const fiber = new Fiber(null, this, false, target); + fiber.shouldPatch = false; if (!__owl__.vnode) { this.__prepareAndRender(fiber); } @@ -3641,7 +3691,7 @@ // currentFiber that is already rendered (isRendered is true), so we are // about to be mounted const isMounted = __owl__.isMounted; - const fiber = new Fiber(null, this, undefined, undefined, force, null); + const fiber = new Fiber(null, this, force, null); Promise.resolve().then(() => { if (__owl__.isMounted || !isMounted) { // we are mounted (__owl__.isMounted), or if we are currently being @@ -3742,13 +3792,6 @@ } __callMounted() { const __owl__ = this.__owl__; - const children = __owl__.children; - for (let id in children) { - const comp = children[id]; - if (!comp.__owl__.isMounted && this.el.contains(comp.el)) { - comp.__callMounted(); - } - } __owl__.isMounted = true; __owl__.currentFiber = null; this.mounted(); @@ -3763,6 +3806,10 @@ } this.willUnmount(); __owl__.isMounted = false; + if (this.__owl__.currentFiber) { + this.__owl__.currentFiber.isCompleted = true; + this.__owl__.currentFiber.root.counter = 0; + } const children = __owl__.children; for (let id in children) { const comp = children[id]; @@ -3775,17 +3822,20 @@ * The __updateProps method is called by the t-component directive whenever * it updates a component (so, when the parent template is rerendered). */ - async __updateProps(nextProps, parentFiber, scope, vars, previousSibling) { + async __updateProps(nextProps, parentFiber, scope, vars) { + this.__owl__.scope = scope; + this.__owl__.vars = vars; const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps); if (shouldUpdate) { const __owl__ = this.__owl__; - const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force, null); + const fiber = new Fiber(parentFiber, this, parentFiber.force, null); if (!parentFiber.child) { parentFiber.child = fiber; } else { - previousSibling.sibling = fiber; + parentFiber.lastChild.sibling = fiber; } + parentFiber.lastChild = fiber; const defaultProps = this.constructor.defaultProps; if (defaultProps) { this.__applyDefaultProps(nextProps, defaultProps); @@ -3818,15 +3868,18 @@ * subcomponent is created. It gets its scope and vars, if any, from the * parent template. */ - __prepare(parentFiber, scope, vars, previousSibling) { - const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force, null); + __prepare(parentFiber, scope, vars) { + this.__owl__.scope = scope; + this.__owl__.vars = vars; + const fiber = new Fiber(parentFiber, this, parentFiber.force, null); fiber.shouldPatch = false; if (!parentFiber.child) { parentFiber.child = fiber; } else { - previousSibling.sibling = fiber; + parentFiber.lastChild.sibling = fiber; } + parentFiber.lastChild = fiber; return this.__prepareAndRender(fiber); } __getTemplate(qweb) { @@ -3908,25 +3961,6 @@ fiber.handleError(error); } } - /** - * Only called by qweb t-component directive - */ - __mount(fiber, elm) { - if (fiber !== this.__owl__.currentFiber) { - fiber = this.__owl__.currentFiber; // TODO: check if we can remove fiber arg - } - const vnode = fiber.vnode; - const __owl__ = this.__owl__; - if (__owl__.classObj) { - vnode.data.class = Object.assign(vnode.data.class || {}, __owl__.classObj); - } - __owl__.vnode = patch(elm, vnode); - __owl__.currentFiber = null; - if (__owl__.parent.__owl__.isMounted && !__owl__.isMounted) { - this.__callMounted(); - } - return __owl__.vnode; - } /** * Only called by qweb t-component directive (when t-keepalive is set) */ @@ -4086,7 +4120,7 @@ } }); const __destroy = component.__destroy; - component.__destroy = (parent) => { + component.__destroy = parent => { ctx.off("update", component); delete mapping[id]; __destroy.call(component, parent); @@ -4254,32 +4288,37 @@ const isStrictEqual = (a, b) => a === b; function useStore(selector, options = {}) { const component = Component.current; + const componentId = component.__owl__.id; const store = options.store || component.env.store; if (!(store instanceof Store)) { throw new Error(`No store found when connecting '${component.constructor.name}'`); } let result = selector(store.state, component.props); const hashFn = store.observer.revNumber.bind(store.observer); - let revNumber = hashFn(result) || result; + let revNumber = hashFn(result); const isEqual = options.isEqual || isStrictEqual; - if (!store.updateFunctions[component.__owl__.id]) { - store.updateFunctions[component.__owl__.id] = []; + if (!store.updateFunctions[componentId]) { + store.updateFunctions[componentId] = []; } - const updateFunctions = store.updateFunctions[component.__owl__.id]; - updateFunctions.push(function () { + function selectCompareUpdate(state, props) { const oldResult = result; - result = selector(store.state, component.props); + result = selector(state, props); const newRevNumber = hashFn(result); - if ((newRevNumber > 0 && revNumber !== newRevNumber) || - (newRevNumber === 0 && !isEqual(oldResult, result))) { + if ((newRevNumber > 0 && revNumber !== newRevNumber) || !isEqual(oldResult, result)) { revNumber = newRevNumber; + if (options.onUpdate) { + options.onUpdate(result); + } return true; } return false; + } + store.updateFunctions[componentId].push(function () { + return selectCompareUpdate(store.state, component.props); }); useContextWithCB(store, component, function () { let shouldRender = false; - for (let fn of updateFunctions) { + for (let fn of store.updateFunctions[componentId]) { shouldRender = fn() || shouldRender; } if (shouldRender) { @@ -4287,16 +4326,22 @@ } }); onWillUpdateProps(props => { - delete store.updateFunctions[component.__owl__.id]; - result = selector(store.state, props); + selectCompareUpdate(store.state, props); }); + const __destroy = component.__destroy; + component.__destroy = parent => { + delete store.updateFunctions[componentId]; + __destroy.call(component, parent); + }; + if (typeof result !== "object") { + return result; + } return new Proxy(result, { get(target, k) { return result[k]; }, set(target, k, v) { - result[k] = v; - return true; + throw new Error("Store state should only be modified through actions"); } }); } @@ -4650,9 +4695,9 @@ exports.useState = useState$1; exports.utils = utils; - exports.__info__.version = '1.0.0-alpha4'; - exports.__info__.date = '2019-11-22T12:03:23.892Z'; - exports.__info__.hash = 'ff76747'; + exports.__info__.version = '1.0.0-alpha5'; + exports.__info__.date = '2019-11-29T15:12:14.501Z'; + exports.__info__.hash = 'c126928'; exports.__info__.url = 'https://github.com/odoo/owl'; }(this.owl = this.owl || {})); diff --git a/playground/samples.js b/playground/samples.js index ab1384f6..7ac567b7 100644 --- a/playground/samples.js +++ b/playground/samples.js @@ -2,7 +2,10 @@ const COMPONENTS = `// In this example, we show how components can be defined an const { Component, useState } = owl; class Greeter extends Component { - state = useState({ word: 'Hello' }); + constructor() { + super(...arguments); + this.state = useState({ word: 'Hello' }); + } toggle() { this.state.word = this.state.word === 'Hi' ? 'Hello' : 'Hi'; @@ -11,7 +14,10 @@ class Greeter extends Component { // Main root component class App extends Component { - state = useState({ name: 'World'}); + constructor() { + super(...arguments); + this.state = useState({ name: 'World'}); + } } App.components = { Greeter }; @@ -47,7 +53,10 @@ const ANIMATION = `// The goal of this component is to see how the t-transition const { Component, useState } = owl; class Counter extends Component { - state = useState({ value: 0 }); + constructor() { + super(...arguments); + this.state = useState({ value: 0 }); + } increment() { this.state.value++; @@ -55,7 +64,10 @@ class Counter extends Component { } class App extends Component { - state = useState({ flag: false, componentFlag: false, numbers: [] }); + constructor() { + super(...arguments); + this.state = useState({ flag: false, componentFlag: false, numbers: [] }); + } toggle(key) { this.state[key] = !this.state[key]; @@ -213,7 +225,10 @@ class DemoComponent extends Component { } class App extends Component { - state = useState({ n: 0, flag: true }); + constructor() { + super(...arguments); + this.state = useState({ n: 0, flag: true }); + } increment() { this.state.n++; @@ -282,11 +297,14 @@ function useMouse() { // Main root component class App extends owl.Component { - // simple state hook (reactive object) - counter = useState({ value: 0 }); + constructor() { + super(...arguments); + // simple state hook (reactive object) + this.counter = useState({ value: 0 }); - // this hooks is bound to the 'mouse' property. - mouse = useMouse(); + // this hooks is bound to the 'mouse' property. + this.mouse = useMouse(); + } increment() { this.counter.value++; @@ -318,7 +336,10 @@ const { Component, Context } = owl; const { useContext } = owl.hooks; class ToolbarButton extends Component { - theme = useContext(this.env.themeContext); + constructor() { + super(...arguments); + this.theme = useContext(this.env.themeContext); + } get style () { const theme = this.theme; @@ -451,12 +472,11 @@ const actions = { // TodoItem //------------------------------------------------------------------------------ class TodoItem extends Component { - state = useState({ isEditing: false }); - dispatch = useDispatch(); - - constructor(...args) { - super(...args); + constructor() { + super(...arguments); useAutofocus("input"); + this.state = useState({ isEditing: false }); + this.dispatch = useDispatch(); } handleKeyup(ev) { @@ -483,9 +503,12 @@ class TodoItem extends Component { // TodoApp //------------------------------------------------------------------------------ class TodoApp extends Component { - state = useState({ filter: "all" }); - todos = useStore(state => state.todos); - dispatch = useDispatch(); + constructor() { + super(...arguments); + this.state = useState({ filter: "all" }); + this.todos = useStore(state => state.todos); + this.dispatch = useDispatch(); + } get visibleTodos() { switch (this.state.filter) { @@ -1008,7 +1031,10 @@ class FormView extends owl.Component {} FormView.components = { AdvancedComponent }; class Chatter extends owl.Component { - messages = Array.from(Array(100).keys()); + constructor() { + super(...arguments); + this.messages = Array.from(Array(100).keys()); + } } class App extends owl.Component {} @@ -1150,7 +1176,10 @@ const SLOTS = `// We show here how slots can be used to create generic component const { Component, useState } = owl; class Card extends Component { - state = useState({ showContent: true }); + constructor() { + super(...arguments); + this.state = useState({ showContent: true }); + } toggleDisplay() { this.state.showContent = !this.state.showContent; @@ -1158,7 +1187,10 @@ class Card extends Component { } class Counter extends Component { - state = useState({val: 1}); + constructor() { + super(...arguments); + this.state = useState({val: 1}); + } inc() { this.state.val++; @@ -1167,7 +1199,10 @@ class Counter extends Component { // Main root component class App extends Component { - state = useState({a: 1, b: 3}); + constructor() { + super(...arguments); + this.state = useState({a: 1, b: 3}); + } inc(key, delta) { this.state[key] += delta; @@ -1277,7 +1312,10 @@ class SlowComponent extends Component { class NotificationList extends Component {} class App extends Component { - state = useState({ value: 0, notifs: [] }); + constructor() { + super(...arguments); + this.state = useState({ value: 0, notifs: [] }); + } increment() { this.state.value++; @@ -1350,13 +1388,16 @@ const FORM = `// This example illustrate how the t-model directive can be used t const { Component, useState } = owl; class Form extends Component { - state = useState({ - text: "", - othertext: "", - number: 11, - color: "", - bool: false - }); + constructor() { + super(...arguments); + this.state = useState({ + text: "", + othertext: "", + number: 11, + color: "", + bool: false + }); + } } // Application setup @@ -1419,7 +1460,10 @@ const { useRef } = owl.hooks; class HelloWorld extends Component {} class Counter extends Component { - state = useState({ value: 0 }); + constructor() { + super(...arguments); + this.state = useState({ value: 0 }); + } inc() { this.state.value++; @@ -1470,11 +1514,14 @@ class Window extends Component { } class WindowManager extends Component { - windows = []; - nextId = 1; - currentZindex = 1; - nextLeft = 0; - nextTop = 0; + constructor() { + super(...arguments); + this.windows = []; + this.nextId = 1; + this.currentZindex = 1; + this.nextLeft = 0; + this.nextTop = 0; + } addWindow(name) { const info = this.env.windows.find(w => w.name === name); @@ -1518,7 +1565,10 @@ class WindowManager extends Component { WindowManager.components = { Window }; class App extends Component { - wmRef = useRef("wm"); + constructor() { + super(...arguments); + this.wmRef = useRef("wm"); + } addWindow(name) { this.wmRef.comp.addWindow(name); @@ -1552,7 +1602,7 @@ const WMS_XML = `
- × + ×