diff --git a/owl.js b/owl.js index b4dc701f..4d99e2a6 100644 --- a/owl.js +++ b/owl.js @@ -2953,7 +2953,7 @@ // hack: specify empty remove hook to prevent the node from being removed from the DOM ctx.addLine(`let pvnode = h('dummy', {key: ${templateId}, hook: {insert(vn) { let nvn=w${componentID}.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});`); ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`); - ctx.addLine(`def${defID}.then(function () {if (w${componentID}.__owl__.isDestroyed) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});`); + ctx.addLine(`def${defID}.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});`); if (registerCode) { ctx.addLine(registerCode); } @@ -3257,7 +3257,14 @@ break; } } - let isValid = isValidProp(props[propName], propsDef[propName]); + let isValid; + try { + isValid = isValidProp(props[propName], propsDef[propName]); + } + catch (e) { + e.message = `Invalid prop '${propName}' in component ${Widget.name} (${e.message})`; + throw e; + } if (!isValid) { throw new Error(`Props '${propName}' of invalid type in component '${Widget.name}'`); } @@ -3297,6 +3304,9 @@ return result; } // propsDef is an object + if (propDef.optional && prop === undefined) { + return true; + } let result = isValidProp(prop, propDef.type); if (propDef.type === Array) { for (let i = 0, iLen = prop.length; i < iLen; i++) { @@ -3308,6 +3318,13 @@ for (let key in shape) { result = result && isValidProp(prop[key], shape[key]); } + if (result) { + for (let propName in prop) { + if (!(propName in shape)) { + throw new Error(`unknown prop '${propName}'`); + } + } + } } return result; } @@ -3478,17 +3495,15 @@ * * Note that a component can be mounted an unmounted several times */ - async mount(target, renderBeforeRemount = false) { + async mount(target) { const __owl__ = this.__owl__; if (__owl__.isMounted) { return Promise.resolve(); } - if (__owl__.vnode && !renderBeforeRemount) { - target.appendChild(this.el); - if (document.body.contains(target)) { - this.__callMounted(); - } - return; + if (!(target instanceof HTMLElement)) { + 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); } return new Promise((resolve, reject) => { const fiber = new Fiber(null, this, undefined, undefined, false); @@ -3632,6 +3647,9 @@ } __owl__.isDestroyed = true; delete __owl__.vnode; + if (__owl__.currentFiber) { + __owl__.currentFiber.isCancelled = true; + } } __callMounted() { const __owl__ = this.__owl__; @@ -3838,6 +3856,8 @@ Component.current = null; Component.components = {}; Component.env = {}; + // expose scheduler s.t. it can be mocked for testing purposes + Component.scheduler = scheduler; /** * Owl Hook System @@ -3926,7 +3946,13 @@ return { get el() { const val = __owl__.refs && __owl__.refs[name]; - return val instanceof HTMLElement ? val : null; + if (val instanceof HTMLElement) { + return val; + } + else if (val instanceof Component) { + return val.el; + } + return null; }, get comp() { const val = __owl__.refs && __owl__.refs[name]; @@ -4123,6 +4149,9 @@ function useStore(selector, options = {}) { const component = Component.current; 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; @@ -4515,9 +4544,9 @@ exports.useState = useState$1; exports.utils = utils; - exports.__info__.version = '1.0.0-alpha2'; - exports.__info__.date = '2019-11-01T08:12:51.280Z'; - exports.__info__.hash = 'bd3c126'; + exports.__info__.version = '1.0.0-alpha3'; + exports.__info__.date = '2019-11-13T14:44:19.699Z'; + exports.__info__.hash = 'd249f50'; exports.__info__.url = 'https://github.com/odoo/owl'; }(this.owl = this.owl || {})); diff --git a/playground/app.js b/playground/app.js index d07ca6ec..f4d841fc 100644 --- a/playground/app.js +++ b/playground/app.js @@ -70,13 +70,9 @@ if __name__ == "__main__": /** * Make an iframe, with all the js, css and xml properly injected. */ -function makeCodeIframe(js, css, xml, errorHandler) { - // check templates - var qweb = new owl.QWeb(); +function makeCodeIframe(js, css, xml) { const sanitizedXML = xml.replace(//g, ""); - // will throw error if there is something wrong with xml - qweb.addTemplates(sanitizedXML); // create iframe const iframe = document.createElement("iframe"); @@ -92,21 +88,13 @@ function makeCodeIframe(js, css, xml, errorHandler) { script.type = "text/javascript"; const content = ` { - owl.__info__.mode = 'dev'; + owl.config.mode = 'dev'; let templates = \`${sanitizedXML}\`; const qweb = new owl.QWeb({ templates }); owl.Component.env = { qweb }; } ${js}`; script.innerHTML = content; - iframe.contentWindow.addEventListener("error", errorHandler); - iframe.contentWindow.addEventListener("unhandledrejection", errorHandler); - setTimeout(function() { - if (iframe.contentWindow) { - iframe.contentWindow.removeEventListener("error", errorHandler); - iframe.contentWindow.removeEventListener("unhandledrejection", errorHandler); - } - }, 200); doc.body.appendChild(script); }); doc.head.appendChild(owlScript); @@ -317,7 +305,6 @@ class App extends owl.Component { js: this.SAMPLES[0].code, css: this.SAMPLES[0].css || "", xml: this.SAMPLES[0].xml || DEFAULT_XML, - error: false, displayWelcome: true, splitLayout: true, leftPaneWidth: Math.ceil(window.innerWidth / 2), @@ -330,37 +317,12 @@ class App extends owl.Component { this.content = useRef("content"); } - displayError(error) { - this.state.error = error; - if (error) { - setTimeout(() => { - this.content.el.innerHTML = ""; - }); - return; - } - } - runCode() { - this.state.displayWelcome = false; - let subiframe; - let error = false; - const errorHandler = e => this.displayError(e.message || e.reason.message); - try { - const { js, css, xml } = this.state; - subiframe = makeCodeIframe(js, css, xml, errorHandler); - } catch (e) { - //probably problem with the templates - error = e; - // we still log the error, always useful to have it available - console.error(e); - } - if (error) { - this.displayError(error.message); - return; - } else { - this.state.error = false; - } this.content.el.innerHTML = ""; + this.state.displayWelcome = false; + + const { js, css, xml } = this.state; + const subiframe = makeCodeIframe(js, css, xml); this.content.el.appendChild(subiframe); } diff --git a/playground/playground.css b/playground/playground.css index 5c469019..bff9db39 100644 --- a/playground/playground.css +++ b/playground/playground.css @@ -179,16 +179,3 @@ body { padding: 5%; } -.right-pane .error { - height: 100%; - width: 90%; - padding-top: 30%; - font-size: 18px; - color: darkred; - margin-left: 5%; -} - -.right-pane .error pre { - overflow: auto; - width: 100%; -} diff --git a/playground/templates.xml b/playground/templates.xml index b42bd715..c0c1300a 100644 --- a/playground/templates.xml +++ b/playground/templates.xml @@ -51,10 +51,6 @@
-