mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] owl: update to v0.17.0
This commit is contained in:
@@ -1170,7 +1170,27 @@
|
|||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const doc = parser.parseFromString(xml, "text/xml");
|
const doc = parser.parseFromString(xml, "text/xml");
|
||||||
if (doc.getElementsByTagName("parsererror").length) {
|
if (doc.getElementsByTagName("parsererror").length) {
|
||||||
throw new Error("Invalid XML in template");
|
let msg = "Invalid XML in template.";
|
||||||
|
const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
|
||||||
|
if (parsererrorText) {
|
||||||
|
msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
|
||||||
|
const re = /\d+/g;
|
||||||
|
const firstMatch = re.exec(parsererrorText);
|
||||||
|
if (firstMatch) {
|
||||||
|
const lineNumber = Number(firstMatch[0]);
|
||||||
|
const line = xml.split("\n")[lineNumber - 1];
|
||||||
|
const secondMatch = re.exec(parsererrorText);
|
||||||
|
if (line && secondMatch) {
|
||||||
|
const columnIndex = Number(secondMatch[0]) - 1;
|
||||||
|
if (line[columnIndex]) {
|
||||||
|
msg +=
|
||||||
|
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||||
|
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
@@ -1300,44 +1320,63 @@
|
|||||||
}
|
}
|
||||||
return template.fn.call(this, context, extra);
|
return template.fn.call(this, context, extra);
|
||||||
}
|
}
|
||||||
_compile(name, elem, parentNode) {
|
/**
|
||||||
|
* Render a template to a html string.
|
||||||
|
*
|
||||||
|
* Note that this is more limited than the `render` method: it is not suitable
|
||||||
|
* to render a full component tree, since this is an asynchronous operation.
|
||||||
|
* This method can only render templates without components.
|
||||||
|
*/
|
||||||
|
renderToString(name, context = {}) {
|
||||||
|
const vnode = this.render(name, context);
|
||||||
|
if (vnode.sel === undefined) {
|
||||||
|
return vnode.text;
|
||||||
|
}
|
||||||
|
const node = document.createElement(vnode.sel);
|
||||||
|
const result = patch(node, vnode);
|
||||||
|
return result.elm.outerHTML;
|
||||||
|
}
|
||||||
|
_compile(name, elem, parentContext) {
|
||||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||||
const ctx = new Context(name);
|
const ctx = new Context(name);
|
||||||
if (parentNode) {
|
if (parentContext) {
|
||||||
ctx.nextID = parentNode + 1;
|
ctx.variables = Object.create(parentContext.variables);
|
||||||
ctx.parentNode = parentNode;
|
ctx.nextID = parentContext.parentNode + 1;
|
||||||
|
ctx.parentNode = parentContext.parentNode;
|
||||||
ctx.allowMultipleRoots = true;
|
ctx.allowMultipleRoots = true;
|
||||||
ctx.addLine(`let c${parentNode} = extra.parentNode;`);
|
ctx.hasParentWidget = true;
|
||||||
|
ctx.addLine(`let c${ctx.parentNode} = extra.parentNode;`);
|
||||||
|
for (let v in parentContext.variables) {
|
||||||
|
let variable = parentContext.variables[v];
|
||||||
|
if (variable.id) {
|
||||||
|
ctx.addLine(`let ${variable.id} = extra.vars.${variable.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parentContext) {
|
||||||
|
ctx.addLine(" Object.assign(context, extra.scope);");
|
||||||
}
|
}
|
||||||
this._compileNode(elem, ctx);
|
this._compileNode(elem, ctx);
|
||||||
if (ctx.shouldProtectContext) {
|
if (!parentContext) {
|
||||||
ctx.code.unshift(" context = Object.create(context);");
|
if (ctx.shouldDefineResult) {
|
||||||
}
|
ctx.addLine(`return result;`);
|
||||||
if (ctx.shouldDefineOwner) {
|
}
|
||||||
// this is necessary to prevent some directives (t-forach for ex) to
|
else {
|
||||||
// pollute the rendering context by adding some keys in it.
|
if (!ctx.rootNode) {
|
||||||
ctx.code.unshift(" let owner = context;");
|
throw new Error(`A template should have one root node (${ctx.templateName})`);
|
||||||
}
|
}
|
||||||
if (ctx.shouldDefineQWeb) {
|
ctx.addLine(`return vn${ctx.rootNode};`);
|
||||||
ctx.code.unshift(" let QWeb = this.constructor;");
|
|
||||||
}
|
|
||||||
if (ctx.shouldDefineUtils) {
|
|
||||||
ctx.code.unshift(" let utils = this.utils;");
|
|
||||||
}
|
|
||||||
if (!parentNode) {
|
|
||||||
if (!ctx.rootNode) {
|
|
||||||
throw new Error("A template should have one root node");
|
|
||||||
}
|
}
|
||||||
ctx.addLine(`return vn${ctx.rootNode};`);
|
|
||||||
}
|
}
|
||||||
|
let code = ctx.generateCode();
|
||||||
let template;
|
let template;
|
||||||
try {
|
try {
|
||||||
template = new Function("context", "extra", ctx.code.join("\n"));
|
template = new Function("context", "extra", code.join("\n"));
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
const templateName = ctx.templateName.replace(/`/g, "'");
|
const templateName = ctx.templateName.replace(/`/g, "'");
|
||||||
console.groupCollapsed(`Invalid Code generated by ${templateName}`);
|
console.groupCollapsed(`Invalid Code generated by ${templateName}`);
|
||||||
console.warn(ctx.code.join("\n"));
|
console.warn(code.join("\n"));
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
throw new Error(`Invalid generated code while compiling template '${templateName}': ${e.message}`);
|
throw new Error(`Invalid generated code while compiling template '${templateName}': ${e.message}`);
|
||||||
}
|
}
|
||||||
@@ -1657,12 +1696,17 @@
|
|||||||
this.rootNode = null;
|
this.rootNode = null;
|
||||||
this.indentLevel = 0;
|
this.indentLevel = 0;
|
||||||
this.shouldDefineOwner = false;
|
this.shouldDefineOwner = false;
|
||||||
|
this.shouldDefineParent = false;
|
||||||
this.shouldDefineQWeb = false;
|
this.shouldDefineQWeb = false;
|
||||||
this.shouldDefineUtils = false;
|
this.shouldDefineUtils = false;
|
||||||
|
this.shouldDefineResult = false;
|
||||||
this.shouldProtectContext = false;
|
this.shouldProtectContext = false;
|
||||||
|
this.shouldTrackScope = false;
|
||||||
this.inLoop = false;
|
this.inLoop = false;
|
||||||
this.inPreTag = false;
|
this.inPreTag = false;
|
||||||
this.allowMultipleRoots = false;
|
this.allowMultipleRoots = false;
|
||||||
|
this.hasParentWidget = false;
|
||||||
|
this.scopeVars = [];
|
||||||
this.rootContext = this;
|
this.rootContext = this;
|
||||||
this.templateName = name || "noname";
|
this.templateName = name || "noname";
|
||||||
this.addLine("var h = this.utils.h;");
|
this.addLine("var h = this.utils.h;");
|
||||||
@@ -1671,6 +1715,44 @@
|
|||||||
const id = this.rootContext.nextID++;
|
const id = this.rootContext.nextID++;
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
generateCode() {
|
||||||
|
const shouldTrackScope = this.shouldTrackScope && this.scopeVars.length;
|
||||||
|
if (shouldTrackScope) {
|
||||||
|
// add some vars to scope if needed
|
||||||
|
for (let scopeVar of this.scopeVars.reverse()) {
|
||||||
|
let { index, key, indent } = scopeVar;
|
||||||
|
const prefix = new Array(indent + 2).join(" ");
|
||||||
|
this.code.splice(index + 1, 0, prefix + `scope.${key} = context.${key};`);
|
||||||
|
}
|
||||||
|
this.code.unshift(" const scope = Object.create(null);");
|
||||||
|
}
|
||||||
|
if (this.shouldProtectContext) {
|
||||||
|
this.code.unshift(" context = Object.create(context);");
|
||||||
|
}
|
||||||
|
if (this.shouldDefineResult) {
|
||||||
|
this.code.unshift(" let result;");
|
||||||
|
}
|
||||||
|
if (this.shouldDefineOwner) {
|
||||||
|
// this is necessary to prevent some directives (t-forach for ex) to
|
||||||
|
// pollute the rendering context by adding some keys in it.
|
||||||
|
this.code.unshift(" let owner = context;");
|
||||||
|
}
|
||||||
|
if (this.shouldDefineParent) {
|
||||||
|
if (this.hasParentWidget) {
|
||||||
|
this.code.unshift(" let parent = extra.parent;");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.code.unshift(" let parent = context;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.shouldDefineQWeb) {
|
||||||
|
this.code.unshift(" let QWeb = this.constructor;");
|
||||||
|
}
|
||||||
|
if (this.shouldDefineUtils) {
|
||||||
|
this.code.unshift(" let utils = this.utils;");
|
||||||
|
}
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
withParent(node) {
|
withParent(node) {
|
||||||
if (!this.allowMultipleRoots &&
|
if (!this.allowMultipleRoots &&
|
||||||
this === this.rootContext &&
|
this === this.rootContext &&
|
||||||
@@ -1696,6 +1778,11 @@
|
|||||||
addLine(line) {
|
addLine(line) {
|
||||||
const prefix = new Array(this.indentLevel + 2).join(" ");
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
||||||
this.code.push(prefix + line);
|
this.code.push(prefix + line);
|
||||||
|
return this.code.length - 1;
|
||||||
|
}
|
||||||
|
addToScope(key, expr) {
|
||||||
|
const index = this.addLine(`context.${key} = ${expr};`);
|
||||||
|
this.rootContext.scopeVars.push({ index, key, indent: this.indentLevel });
|
||||||
}
|
}
|
||||||
addIf(condition) {
|
addIf(condition) {
|
||||||
this.addLine(`if (${condition}) {`);
|
this.addLine(`if (${condition}) {`);
|
||||||
@@ -1778,9 +1865,6 @@
|
|||||||
if (defaultProps) {
|
if (defaultProps) {
|
||||||
props = this.__applyDefaultProps(props, defaultProps);
|
props = this.__applyDefaultProps(props, defaultProps);
|
||||||
}
|
}
|
||||||
if (QWeb.dev) {
|
|
||||||
this.__validateProps(props || {});
|
|
||||||
}
|
|
||||||
// is this a good idea?
|
// is this a good idea?
|
||||||
// Pro: if props is empty, we can create easily a component
|
// Pro: if props is empty, we can create easily a component
|
||||||
// Con: this is not really safe
|
// Con: this is not really safe
|
||||||
@@ -1795,6 +1879,11 @@
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.env = parent;
|
this.env = parent;
|
||||||
|
if (QWeb.dev) {
|
||||||
|
// we only validate props for root widgets here. "Regular" widget
|
||||||
|
// props are validated by the t-component directive
|
||||||
|
UTILS.validateProps(this.constructor, this.props);
|
||||||
|
}
|
||||||
this.env.qweb.on("update", this, () => {
|
this.env.qweb.on("update", this, () => {
|
||||||
if (this.__owl__.isMounted) {
|
if (this.__owl__.isMounted) {
|
||||||
this.render(true);
|
this.render(true);
|
||||||
@@ -1901,6 +1990,11 @@
|
|||||||
* @see mounted
|
* @see mounted
|
||||||
*/
|
*/
|
||||||
willUnmount() { }
|
willUnmount() { }
|
||||||
|
/**
|
||||||
|
* catchError is a method called whenever some error happens in the rendering or
|
||||||
|
* lifecycle hooks of a child.
|
||||||
|
*/
|
||||||
|
catchError(error) { }
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Public
|
// Public
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -1928,7 +2022,7 @@
|
|||||||
this.el.remove();
|
this.el.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async render(force = false, patchQueue) {
|
async render(force = false, patchQueue, scope, vars) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
if (!__owl__.isMounted) {
|
if (!__owl__.isMounted) {
|
||||||
return;
|
return;
|
||||||
@@ -1937,7 +2031,7 @@
|
|||||||
if (shouldPatch) {
|
if (shouldPatch) {
|
||||||
patchQueue = [];
|
patchQueue = [];
|
||||||
}
|
}
|
||||||
const renderVDom = this.__render(force, patchQueue);
|
const renderVDom = this.__render(force, patchQueue, scope, vars);
|
||||||
const renderId = __owl__.renderId;
|
const renderId = __owl__.renderId;
|
||||||
await renderVDom;
|
await renderVDom;
|
||||||
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
|
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
|
||||||
@@ -2044,7 +2138,12 @@
|
|||||||
for (let key in handlers) {
|
for (let key in handlers) {
|
||||||
handlers[key]();
|
handlers[key]();
|
||||||
}
|
}
|
||||||
this.mounted();
|
try {
|
||||||
|
this.mounted();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
errorHandler(e, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
__callWillUnmount() {
|
__callWillUnmount() {
|
||||||
this.willUnmount();
|
this.willUnmount();
|
||||||
@@ -2058,19 +2157,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async __updateProps(nextProps, forceUpdate = false, patchQueue) {
|
async __updateProps(nextProps, forceUpdate = false, patchQueue, scope, vars) {
|
||||||
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
const shouldUpdate = forceUpdate || this.shouldUpdate(nextProps);
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
const defaultProps = this.constructor.defaultProps;
|
const defaultProps = this.constructor.defaultProps;
|
||||||
if (defaultProps) {
|
if (defaultProps) {
|
||||||
nextProps = this.__applyDefaultProps(nextProps, defaultProps);
|
nextProps = this.__applyDefaultProps(nextProps, defaultProps);
|
||||||
}
|
}
|
||||||
if (QWeb.dev) {
|
|
||||||
this.__validateProps(nextProps);
|
|
||||||
}
|
|
||||||
await this.willUpdateProps(nextProps);
|
await this.willUpdateProps(nextProps);
|
||||||
this.props = nextProps;
|
this.props = nextProps;
|
||||||
await this.render(forceUpdate, patchQueue);
|
await this.render(forceUpdate, patchQueue, scope, vars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
__patch(vnode) {
|
__patch(vnode) {
|
||||||
@@ -2082,14 +2178,20 @@
|
|||||||
}
|
}
|
||||||
__owl__.vnode = patch(target, vnode);
|
__owl__.vnode = patch(target, vnode);
|
||||||
}
|
}
|
||||||
__prepare() {
|
__prepare(scope, vars) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
__owl__.renderProps = this.props;
|
__owl__.renderProps = this.props;
|
||||||
__owl__.renderPromise = this.__prepareAndRender();
|
__owl__.renderPromise = this.__prepareAndRender(scope, vars);
|
||||||
return __owl__.renderPromise;
|
return __owl__.renderPromise;
|
||||||
}
|
}
|
||||||
async __prepareAndRender() {
|
async __prepareAndRender(scope, vars) {
|
||||||
await this.willStart();
|
try {
|
||||||
|
await this.willStart();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
errorHandler(e, this);
|
||||||
|
return Promise.resolve(h("div"));
|
||||||
|
}
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
if (__owl__.isDestroyed) {
|
if (__owl__.isDestroyed) {
|
||||||
return Promise.resolve(h("div"));
|
return Promise.resolve(h("div"));
|
||||||
@@ -2122,9 +2224,9 @@
|
|||||||
}
|
}
|
||||||
__owl__.render = qweb.render.bind(qweb, this.template);
|
__owl__.render = qweb.render.bind(qweb, this.template);
|
||||||
this.__observeState();
|
this.__observeState();
|
||||||
return this.__render();
|
return this.__render(false, [], scope, vars);
|
||||||
}
|
}
|
||||||
async __render(force = false, patchQueue = []) {
|
async __render(force = false, patchQueue = [], scope, vars) {
|
||||||
const __owl__ = this.__owl__;
|
const __owl__ = this.__owl__;
|
||||||
__owl__.renderId++;
|
__owl__.renderId++;
|
||||||
const promises = [];
|
const promises = [];
|
||||||
@@ -2135,13 +2237,22 @@
|
|||||||
if (__owl__.observer) {
|
if (__owl__.observer) {
|
||||||
__owl__.observer.allowMutations = false;
|
__owl__.observer.allowMutations = false;
|
||||||
}
|
}
|
||||||
let vnode = __owl__.render(this, {
|
let vnode;
|
||||||
promises,
|
try {
|
||||||
handlers: __owl__.boundHandlers,
|
vnode = __owl__.render(this, {
|
||||||
mountedHandlers: __owl__.mountedHandlers,
|
promises,
|
||||||
forceUpdate: force,
|
handlers: __owl__.boundHandlers,
|
||||||
patchQueue
|
mountedHandlers: __owl__.mountedHandlers,
|
||||||
});
|
forceUpdate: force,
|
||||||
|
patchQueue,
|
||||||
|
scope,
|
||||||
|
vars
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
vnode = __owl__.vnode || h("div");
|
||||||
|
errorHandler(e, this);
|
||||||
|
}
|
||||||
patch.push(vnode);
|
patch.push(vnode);
|
||||||
if (__owl__.observer) {
|
if (__owl__.observer) {
|
||||||
__owl__.observer.allowMutations = true;
|
__owl__.observer.allowMutations = true;
|
||||||
@@ -2211,96 +2322,51 @@
|
|||||||
* 3) Call 'patched' on the component of each patch, in inverse order
|
* 3) Call 'patched' on the component of each patch, in inverse order
|
||||||
*/
|
*/
|
||||||
__applyPatchQueue(patchQueue) {
|
__applyPatchQueue(patchQueue) {
|
||||||
const patchLen = patchQueue.length;
|
let component = this;
|
||||||
for (let i = 0; i < patchLen; i++) {
|
try {
|
||||||
const patch = patchQueue[i];
|
const patchLen = patchQueue.length;
|
||||||
patch.push(patch[0].willPatch());
|
for (let i = 0; i < patchLen; i++) {
|
||||||
}
|
const patch = patchQueue[i];
|
||||||
for (let i = 0; i < patchLen; i++) {
|
component = patch[0];
|
||||||
const patch = patchQueue[i];
|
patch.push(patch[0].willPatch());
|
||||||
patch[0].__patch(patch[1]);
|
}
|
||||||
}
|
for (let i = 0; i < patchLen; i++) {
|
||||||
for (let i = patchLen - 1; i >= 0; i--) {
|
const patch = patchQueue[i];
|
||||||
const patch = patchQueue[i];
|
patch[0].__patch(patch[1]);
|
||||||
patch[0].patched(patch[2]);
|
}
|
||||||
}
|
for (let i = patchLen - 1; i >= 0; i--) {
|
||||||
}
|
const patch = patchQueue[i];
|
||||||
/**
|
component = patch[0];
|
||||||
* Validate the component props (or next props) against the (static) props
|
patch[0].patched(patch[2]);
|
||||||
* description. This is potentially an expensive operation: it may needs to
|
|
||||||
* visit recursively the props and all the children to check if they are valid.
|
|
||||||
* This is why it is only done in 'dev' mode.
|
|
||||||
*/
|
|
||||||
__validateProps(props) {
|
|
||||||
const propsDef = this.constructor.props;
|
|
||||||
if (propsDef instanceof Array) {
|
|
||||||
// list of strings (prop names)
|
|
||||||
for (let i = 0, l = propsDef.length; i < l; i++) {
|
|
||||||
if (!(propsDef[i] in props)) {
|
|
||||||
throw new Error(`Missing props '${propsDef[i]}' (component '${this.constructor.name}')`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (propsDef) {
|
catch (e) {
|
||||||
// propsDef is an object now
|
errorHandler(e, component);
|
||||||
for (let propName in propsDef) {
|
|
||||||
if (!(propName in props)) {
|
|
||||||
if (propsDef[propName] && !propsDef[propName].optional) {
|
|
||||||
throw new Error(`Missing props '${propName}' (component '${this.constructor.name}')`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let isValid = isValidProp(props[propName], propsDef[propName]);
|
|
||||||
if (!isValid) {
|
|
||||||
throw new Error(`Props '${propName}' of invalid type in component '${this.constructor.name}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Prop validation helper
|
// Error handling
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/**
|
function errorHandler(error, component) {
|
||||||
* Check if an invidual prop value matches its (static) prop definition
|
let canCatch = false;
|
||||||
*/
|
let qweb = component.env.qweb;
|
||||||
function isValidProp(prop, propDef) {
|
let root = component;
|
||||||
if (typeof propDef === "function") {
|
while (component && !(canCatch = component.catchError !== Component.prototype.catchError)) {
|
||||||
// Check if a value is constructed by some Constructor. Note that there is a
|
root = component;
|
||||||
// slight abuse of language: we want to consider primitive values as well.
|
component = component.__owl__.parent;
|
||||||
//
|
|
||||||
// So, even though 1 is not an instance of Number, we want to consider that
|
|
||||||
// it is valid.
|
|
||||||
if (typeof prop === "object") {
|
|
||||||
return prop instanceof propDef;
|
|
||||||
}
|
|
||||||
return typeof prop === propDef.name.toLowerCase();
|
|
||||||
}
|
}
|
||||||
else if (propDef instanceof Array) {
|
console.error(error);
|
||||||
// If this code is executed, this means that we want to check if a prop
|
// we trigger error on QWeb so it can be logged/handled
|
||||||
// matches at least one of its descriptor.
|
qweb.trigger("error", error);
|
||||||
let result = false;
|
if (canCatch) {
|
||||||
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
setTimeout(() => {
|
||||||
result = result || isValidProp(prop, propDef[i]);
|
component.catchError(error);
|
||||||
}
|
});
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
// propsDef is an object
|
else {
|
||||||
let result = isValidProp(prop, propDef.type);
|
root.destroy();
|
||||||
if (propDef.type === Array) {
|
|
||||||
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
|
||||||
result = result && isValidProp(prop[i], propDef.element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (propDef.type === Object) {
|
|
||||||
const shape = propDef.shape;
|
|
||||||
for (let key in shape) {
|
|
||||||
result = result && isValidProp(prop[key], shape[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2545,11 +2611,11 @@
|
|||||||
ctx.addLine(`var _length${keysID} = _${keysID}.length;`);
|
ctx.addLine(`var _length${keysID} = _${keysID}.length;`);
|
||||||
ctx.addLine(`for (let i = 0; i < _length${keysID}; i++) {`);
|
ctx.addLine(`for (let i = 0; i < _length${keysID}; i++) {`);
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
ctx.addLine(`context.${name}_first = i === 0;`);
|
ctx.addToScope(name + '_first', 'i === 0');
|
||||||
ctx.addLine(`context.${name}_last = i === _length${keysID} - 1;`);
|
ctx.addToScope(name + '_last', `i === _length${keysID} - 1`);
|
||||||
ctx.addLine(`context.${name}_index = i;`);
|
ctx.addToScope(name + '_index', 'i');
|
||||||
ctx.addLine(`context.${name} = _${keysID}[i];`);
|
ctx.addToScope(name, `_${keysID}[i]`);
|
||||||
ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
|
ctx.addToScope(name + '_value', `_${valuesID}[i]`);
|
||||||
const nodeCopy = node.cloneNode(true);
|
const nodeCopy = node.cloneNode(true);
|
||||||
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
|
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
|
||||||
if (!shouldWarn && node.tagName === "t") {
|
if (!shouldWarn && node.tagName === "t") {
|
||||||
@@ -2761,6 +2827,18 @@
|
|||||||
const T_COMPONENT_MODS_CODE = Object.assign({}, MODS_CODE, {
|
const T_COMPONENT_MODS_CODE = Object.assign({}, MODS_CODE, {
|
||||||
self: "if (e.target !== vn.elm) {return}"
|
self: "if (e.target !== vn.elm) {return}"
|
||||||
});
|
});
|
||||||
|
UTILS.defineProxy = function defineProxy(target, source) {
|
||||||
|
for (let k in source) {
|
||||||
|
Object.defineProperty(target, k, {
|
||||||
|
get() {
|
||||||
|
return source[k];
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
source[k] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* The t-component directive is certainly a complicated and hard to maintain piece
|
* The t-component directive is certainly a complicated and hard to maintain piece
|
||||||
* of code. To help you, fellow developer, if you have to maintain it, I offer
|
* of code. To help you, fellow developer, if you have to maintain it, I offer
|
||||||
@@ -2929,6 +3007,7 @@
|
|||||||
ctx.addLine("//COMPONENT");
|
ctx.addLine("//COMPONENT");
|
||||||
ctx.rootContext.shouldDefineOwner = true;
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
ctx.rootContext.shouldDefineQWeb = true;
|
ctx.rootContext.shouldDefineQWeb = true;
|
||||||
|
ctx.rootContext.shouldDefineParent = true;
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
||||||
let async = node.getAttribute("t-asyncroot") ? true : false;
|
let async = node.getAttribute("t-asyncroot") ? true : false;
|
||||||
@@ -3039,7 +3118,7 @@
|
|||||||
if (tattClass) {
|
if (tattClass) {
|
||||||
let tattExpr = ctx.formatExpression(tattClass);
|
let tattExpr = ctx.formatExpression(tattClass);
|
||||||
if (tattExpr[0] !== "{" || tattExpr[tattExpr.length - 1] !== "}") {
|
if (tattExpr[0] !== "{" || tattExpr[tattExpr.length - 1] !== "}") {
|
||||||
tattExpr = `this.utils.toObj(${tattExpr})`;
|
tattExpr = `utils.toObj(${tattExpr})`;
|
||||||
}
|
}
|
||||||
if (classAttr) {
|
if (classAttr) {
|
||||||
ctx.addLine(`Object.assign(${classObj}, ${tattExpr})`);
|
ctx.addLine(`Object.assign(${classObj}, ${tattExpr})`);
|
||||||
@@ -3085,14 +3164,27 @@
|
|||||||
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr};` : "";
|
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr};` : "";
|
||||||
createHook = `vnode.data.hook = {create(_, vn){${styleCode}${eventsCode}}};`;
|
createHook = `vnode.data.hook = {create(_, vn){${styleCode}${eventsCode}}};`;
|
||||||
}
|
}
|
||||||
ctx.addLine(`let w${componentID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`);
|
ctx.addLine(`let w${componentID} = ${templateID} in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[${templateID}]] : false;`);
|
||||||
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
|
if (ctx.parentNode) {
|
||||||
|
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
|
||||||
|
}
|
||||||
|
let shouldProxy = false;
|
||||||
if (async) {
|
if (async) {
|
||||||
ctx.addLine(`const patchQueue${componentID} = [];`);
|
ctx.addLine(`const patchQueue${componentID} = [];`);
|
||||||
ctx.addLine(`c${ctx.parentNode}.push(w${componentID} && w${componentID}.__owl__.pvnode || null);`);
|
ctx.addLine(`c${ctx.parentNode}.push(w${componentID} && w${componentID}.__owl__.pvnode || null);`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ctx.addLine(`c${ctx.parentNode}.push(null);`);
|
if (ctx.parentNode) {
|
||||||
|
ctx.addLine(`c${ctx.parentNode}.push(null);`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let id = ctx.generateID();
|
||||||
|
ctx.rootContext.rootNode = id;
|
||||||
|
shouldProxy = true;
|
||||||
|
ctx.rootContext.shouldDefineResult = true;
|
||||||
|
ctx.addLine(`let vn${id} = {};`);
|
||||||
|
ctx.addLine(`result = vn${id};`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx.addLine(`let props${componentID} = {${propStr}};`);
|
ctx.addLine(`let props${componentID} = {${propStr}};`);
|
||||||
ctx.addIf(`w${componentID} && w${componentID}.__owl__.renderPromise && !w${componentID}.__owl__.vnode`);
|
ctx.addIf(`w${componentID} && w${componentID}.__owl__.renderPromise && !w${componentID}.__owl__.vnode`);
|
||||||
@@ -3109,10 +3201,21 @@
|
|||||||
ctx.addLine(`let W${componentID} = context.components && context.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];`);
|
ctx.addLine(`let W${componentID} = context.components && context.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];`);
|
||||||
// maybe only do this in dev mode...
|
// 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(`if (!W${componentID}) {throw new Error('Cannot find the definition of component "' + componentKey${componentID} + '"')}`);
|
||||||
ctx.addLine(`w${componentID} = new W${componentID}(owner, props${componentID});`);
|
if (QWeb.dev) {
|
||||||
ctx.addLine(`context.__owl__.cmap[${templateID}] = w${componentID}.__owl__.id;`);
|
ctx.addLine(`utils.validateProps(W${componentID}, props${componentID})`);
|
||||||
|
}
|
||||||
|
ctx.addLine(`w${componentID} = new W${componentID}(parent, props${componentID});`);
|
||||||
|
ctx.addLine(`parent.__owl__.cmap[${templateID}] = w${componentID}.__owl__.id;`);
|
||||||
// SLOTS
|
// SLOTS
|
||||||
if (node.childNodes.length) {
|
const varDefs = [];
|
||||||
|
const hasSlots = node.childNodes.length;
|
||||||
|
if (hasSlots) {
|
||||||
|
ctx.rootContext.shouldTrackScope = true;
|
||||||
|
for (let v of Object.values(ctx.variables)) {
|
||||||
|
if (v["id"]) {
|
||||||
|
varDefs.push(v["id"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
const clone = node.cloneNode(true);
|
const clone = node.cloneNode(true);
|
||||||
const slotNodes = clone.querySelectorAll("[t-set]");
|
const slotNodes = clone.querySelectorAll("[t-set]");
|
||||||
const slotId = qweb.nextSlotId++;
|
const slotId = qweb.nextSlotId++;
|
||||||
@@ -3123,7 +3226,7 @@
|
|||||||
slotNode.parentElement.removeChild(slotNode);
|
slotNode.parentElement.removeChild(slotNode);
|
||||||
const key = slotNode.getAttribute("t-set");
|
const key = slotNode.getAttribute("t-set");
|
||||||
slotNode.removeAttribute("t-set");
|
slotNode.removeAttribute("t-set");
|
||||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx.parentNode);
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||||
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3132,22 +3235,37 @@
|
|||||||
for (let child of Object.values(clone.childNodes)) {
|
for (let child of Object.values(clone.childNodes)) {
|
||||||
t.appendChild(child);
|
t.appendChild(child);
|
||||||
}
|
}
|
||||||
const slotFn = qweb._compile(`slot_default_template`, t, ctx.parentNode);
|
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
||||||
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.addLine(`def${defID} = w${componentID}.__prepare();`);
|
let scopeVars = "";
|
||||||
|
if (hasSlots) {
|
||||||
|
scopeVars += ctx.scopeVars.length ? `Object.assign({}, scope)` : varDefs.length ? `{}` : "";
|
||||||
|
if (varDefs.length) {
|
||||||
|
scopeVars += `, {${varDefs.join(",")}}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.addLine(`def${defID} = w${componentID}.__prepare(${scopeVars});`);
|
||||||
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
||||||
ctx.addLine(`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});c${ctx.parentNode}[_${dummyID}_index]=pvnode;w${componentID}.__owl__.pvnode = pvnode;});`);
|
let registerCode = `c${ctx.parentNode}[_${dummyID}_index]=pvnode;`;
|
||||||
|
if (shouldProxy) {
|
||||||
|
registerCode = `utils.defineProxy(vn${ctx.rootNode}, pvnode);`;
|
||||||
|
}
|
||||||
|
ctx.addLine(`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${componentID}.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});${registerCode}w${componentID}.__owl__.pvnode = pvnode;});`);
|
||||||
ctx.addElse();
|
ctx.addElse();
|
||||||
// need to update component
|
// need to update component
|
||||||
const patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
const patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
||||||
ctx.addLine(`def${defID} = def${defID} || w${componentID}.__updateProps(props${componentID}, extra.forceUpdate, ${patchQueueCode});`);
|
if (QWeb.dev) {
|
||||||
|
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
|
||||||
|
}
|
||||||
|
ctx.addLine(`def${defID} = def${defID} || w${componentID}.__updateProps(props${componentID}, extra.forceUpdate, ${patchQueueCode}${scopeVars &&
|
||||||
|
", " + scopeVars});`);
|
||||||
let keepAliveCode = "";
|
let keepAliveCode = "";
|
||||||
if (keepAlive) {
|
if (keepAlive) {
|
||||||
keepAliveCode = `pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w${componentID}.el,vn.elm);vn.elm=w${componentID}.el;w${componentID}.__remount();};`;
|
keepAliveCode = `pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w${componentID}.el,vn.elm);vn.elm=w${componentID}.el;w${componentID}.__remount();};`;
|
||||||
}
|
}
|
||||||
ctx.addLine(`def${defID} = def${defID}.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};${tattStyle ? `w${componentID}.el.style=${tattStyle};` : ""}let pvnode=w${componentID}.__owl__.pvnode;${keepAliveCode}c${ctx.parentNode}[_${dummyID}_index]=pvnode;});`);
|
ctx.addLine(`def${defID} = def${defID}.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};${tattStyle ? `w${componentID}.el.style=${tattStyle};` : ""}let pvnode=w${componentID}.__owl__.pvnode;${keepAliveCode}${registerCode}});`);
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
if (classObj) {
|
if (classObj) {
|
||||||
ctx.addLine(`w${componentID}.__owl__.classObj=${classObj};`);
|
ctx.addLine(`w${componentID}.__owl__.classObj=${classObj};`);
|
||||||
@@ -3165,6 +3283,100 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
// Prop validation helper
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Validate the component props (or next props) against the (static) props
|
||||||
|
* description. This is potentially an expensive operation: it may needs to
|
||||||
|
* visit recursively the props and all the children to check if they are valid.
|
||||||
|
* This is why it is only done in 'dev' mode.
|
||||||
|
*/
|
||||||
|
UTILS.validateProps = function (Widget, props) {
|
||||||
|
const propsDef = Widget.props;
|
||||||
|
if (propsDef instanceof Array) {
|
||||||
|
// list of strings (prop names)
|
||||||
|
for (let i = 0, l = propsDef.length; i < l; i++) {
|
||||||
|
const propName = propsDef[i];
|
||||||
|
if (propName[propName.length - 1] === "?") {
|
||||||
|
// optional prop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!props[propName]) {
|
||||||
|
throw new Error(`Missing props '${propsDef[i]}' (component '${Widget.name}')`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let key in props) {
|
||||||
|
if (!propsDef.includes(key) && !propsDef.includes(key + "?")) {
|
||||||
|
throw new Error(`Unknown prop '${key}' given to component '${Widget.name}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (propsDef) {
|
||||||
|
// propsDef is an object now
|
||||||
|
for (let propName in propsDef) {
|
||||||
|
if (props[propName] === undefined) {
|
||||||
|
if (propsDef[propName] && !propsDef[propName].optional) {
|
||||||
|
throw new Error(`Missing props '${propName}' (component '${Widget.name}')`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let isValid = isValidProp(props[propName], propsDef[propName]);
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error(`Props '${propName}' of invalid type in component '${Widget.name}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let propName in props) {
|
||||||
|
if (!(propName in propsDef)) {
|
||||||
|
throw new Error(`Unknown prop '${propName}' given to component '${Widget.name}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Check if an invidual prop value matches its (static) prop definition
|
||||||
|
*/
|
||||||
|
function isValidProp(prop, propDef) {
|
||||||
|
if (propDef === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof propDef === "function") {
|
||||||
|
// Check if a value is constructed by some Constructor. Note that there is a
|
||||||
|
// slight abuse of language: we want to consider primitive values as well.
|
||||||
|
//
|
||||||
|
// So, even though 1 is not an instance of Number, we want to consider that
|
||||||
|
// it is valid.
|
||||||
|
if (typeof prop === "object") {
|
||||||
|
return prop instanceof propDef;
|
||||||
|
}
|
||||||
|
return typeof prop === propDef.name.toLowerCase();
|
||||||
|
}
|
||||||
|
else if (propDef instanceof Array) {
|
||||||
|
// If this code is executed, this means that we want to check if a prop
|
||||||
|
// matches at least one of its descriptor.
|
||||||
|
let result = false;
|
||||||
|
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
||||||
|
result = result || isValidProp(prop, propDef[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// propsDef is an object
|
||||||
|
let result = isValidProp(prop, propDef.type);
|
||||||
|
if (propDef.type === Array) {
|
||||||
|
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
||||||
|
result = result && isValidProp(prop[i], propDef.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (propDef.type === Object) {
|
||||||
|
const shape = propDef.shape;
|
||||||
|
for (let key in shape) {
|
||||||
|
result = result && isValidProp(prop[key], shape[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
// t-mounted
|
// t-mounted
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
@@ -3199,9 +3411,10 @@
|
|||||||
priority: 80,
|
priority: 80,
|
||||||
atNodeEncounter({ ctx, value }) {
|
atNodeEncounter({ ctx, value }) {
|
||||||
const slotKey = ctx.generateID();
|
const slotKey = ctx.generateID();
|
||||||
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
|
ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
|
||||||
ctx.addIf(`slot${slotKey}`);
|
ctx.addIf(`slot${slotKey}`);
|
||||||
ctx.addLine(`slot${slotKey}(context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}}));`);
|
ctx.addLine(`slot${slotKey}(context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: extra.vars, parent: owner}));`);
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3371,21 +3584,20 @@
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
function connect(Comp, mapStoreToProps, options = {}) {
|
class ConnectedComponent extends Component {
|
||||||
let hashFunction = options.hashFunction || null;
|
constructor(parent, props) {
|
||||||
const getStore = options.getStore || (env => env.store);
|
super(parent, props);
|
||||||
if (!hashFunction) {
|
this.deep = true;
|
||||||
let deep = "deep" in options ? options.deep : true;
|
this.hashFunction = ({ storeProps }, options) => {
|
||||||
let defaultRevFunction = deep ? deepRevNumber : revNumber;
|
let refFunction = this.deep ? deepRevNumber : revNumber;
|
||||||
hashFunction = function ({ storeProps }, options) {
|
|
||||||
const { currentStoreProps } = options;
|
|
||||||
if ("__owl__" in storeProps) {
|
if ("__owl__" in storeProps) {
|
||||||
return defaultRevFunction(storeProps);
|
return refFunction(storeProps);
|
||||||
}
|
}
|
||||||
|
const { currentStoreProps } = options;
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
for (let key in storeProps) {
|
for (let key in storeProps) {
|
||||||
const val = storeProps[key];
|
const val = storeProps[key];
|
||||||
const hashVal = defaultRevFunction(val);
|
const hashVal = refFunction(val);
|
||||||
if (hashVal === 0) {
|
if (hashVal === 0) {
|
||||||
if (val !== currentStoreProps[key]) {
|
if (val !== currentStoreProps[key]) {
|
||||||
options.didChange = true;
|
options.didChange = true;
|
||||||
@@ -3397,83 +3609,77 @@
|
|||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
};
|
};
|
||||||
|
const store = this.getStore(this.env);
|
||||||
|
const ownProps = Object.assign({}, props || {});
|
||||||
|
const storeProps = this.constructor.mapStoreToProps(store.state, ownProps, store.getters);
|
||||||
|
const mergedProps = Object.assign({}, props || {}, storeProps);
|
||||||
|
this.props = mergedProps;
|
||||||
|
this.__owl__.ownProps = ownProps;
|
||||||
|
this.__owl__.currentStoreProps = storeProps;
|
||||||
|
this.__owl__.store = store;
|
||||||
|
this.__owl__.storeHash = this.hashFunction({
|
||||||
|
state: store.state,
|
||||||
|
storeProps: storeProps,
|
||||||
|
revNumber,
|
||||||
|
deepRevNumber
|
||||||
|
}, {
|
||||||
|
currentStoreProps: storeProps
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const Result = class extends Comp {
|
getStore(env) {
|
||||||
constructor(parent, props) {
|
return env.store;
|
||||||
const env = parent instanceof Component ? parent.env : parent;
|
}
|
||||||
const store = getStore(env);
|
static mapStoreToProps(storeState, ownProps, getters) {
|
||||||
const ownProps = Object.assign({}, props || {});
|
return {};
|
||||||
const storeProps = mapStoreToProps(store.state, ownProps, store.getters);
|
}
|
||||||
const mergedProps = Object.assign({}, props || {}, storeProps);
|
/**
|
||||||
super(parent, mergedProps);
|
* We do not use the mounted hook here for a subtle reason: we want the
|
||||||
this.__owl__.ownProps = ownProps;
|
* updates to be called for the parents before the children. However,
|
||||||
|
* if we use the mounted hook, this will be done in the reverse order.
|
||||||
|
*/
|
||||||
|
__callMounted() {
|
||||||
|
this.__owl__.store.on("update", this, this.__checkUpdate);
|
||||||
|
super.__callMounted();
|
||||||
|
}
|
||||||
|
willUnmount() {
|
||||||
|
this.__owl__.store.off("update", this);
|
||||||
|
super.willUnmount();
|
||||||
|
}
|
||||||
|
async __checkUpdate(updateId) {
|
||||||
|
if (updateId === this.__owl__.currentUpdateId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ownProps = this.__owl__.ownProps;
|
||||||
|
const storeProps = this.constructor.mapStoreToProps(this.__owl__.store.state, ownProps, this.__owl__.store.getters);
|
||||||
|
const options = {
|
||||||
|
currentStoreProps: this.__owl__.currentStoreProps
|
||||||
|
};
|
||||||
|
const storeHash = this.hashFunction({
|
||||||
|
state: this.__owl__.store.state,
|
||||||
|
storeProps: storeProps,
|
||||||
|
revNumber,
|
||||||
|
deepRevNumber
|
||||||
|
}, options);
|
||||||
|
let didChange = options.didChange;
|
||||||
|
if (storeHash !== this.__owl__.storeHash) {
|
||||||
|
didChange = true;
|
||||||
|
this.__owl__.storeHash = storeHash;
|
||||||
|
}
|
||||||
|
if (didChange) {
|
||||||
this.__owl__.currentStoreProps = storeProps;
|
this.__owl__.currentStoreProps = storeProps;
|
||||||
this.__owl__.store = store;
|
await this.__updateProps(ownProps, false);
|
||||||
this.__owl__.storeHash = hashFunction({
|
|
||||||
state: store.state,
|
|
||||||
storeProps: storeProps,
|
|
||||||
revNumber,
|
|
||||||
deepRevNumber
|
|
||||||
}, {
|
|
||||||
currentStoreProps: storeProps
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
/**
|
}
|
||||||
* We do not use the mounted hook here for a subtle reason: we want the
|
__updateProps(nextProps, forceUpdate, patchQueue) {
|
||||||
* updates to be called for the parents before the children. However,
|
const __owl__ = this.__owl__;
|
||||||
* if we use the mounted hook, this will be done in the reverse order.
|
__owl__.currentUpdateId = __owl__.store._updateId;
|
||||||
*/
|
if (__owl__.ownProps !== nextProps) {
|
||||||
__callMounted() {
|
__owl__.currentStoreProps = this.constructor.mapStoreToProps(__owl__.store.state, nextProps, __owl__.store.getters);
|
||||||
this.__owl__.store.on("update", this, this.__checkUpdate);
|
|
||||||
super.__callMounted();
|
|
||||||
}
|
}
|
||||||
willUnmount() {
|
__owl__.ownProps = nextProps;
|
||||||
this.__owl__.store.off("update", this);
|
const mergedProps = Object.assign({}, nextProps, __owl__.currentStoreProps);
|
||||||
super.willUnmount();
|
return super.__updateProps(mergedProps, forceUpdate, patchQueue);
|
||||||
}
|
}
|
||||||
async __checkUpdate(updateId) {
|
|
||||||
if (updateId === this.__owl__.currentUpdateId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ownProps = this.__owl__.ownProps;
|
|
||||||
const storeProps = mapStoreToProps(this.__owl__.store.state, ownProps, this.__owl__.store.getters);
|
|
||||||
const options = {
|
|
||||||
currentStoreProps: this.__owl__.currentStoreProps
|
|
||||||
};
|
|
||||||
const storeHash = hashFunction({
|
|
||||||
state: this.__owl__.store.state,
|
|
||||||
storeProps: storeProps,
|
|
||||||
revNumber,
|
|
||||||
deepRevNumber
|
|
||||||
}, options);
|
|
||||||
let didChange = options.didChange;
|
|
||||||
if (storeHash !== this.__owl__.storeHash) {
|
|
||||||
didChange = true;
|
|
||||||
this.__owl__.storeHash = storeHash;
|
|
||||||
}
|
|
||||||
if (didChange) {
|
|
||||||
this.__owl__.currentStoreProps = storeProps;
|
|
||||||
await this.__updateProps(ownProps, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__updateProps(nextProps, forceUpdate, patchQueue) {
|
|
||||||
const __owl__ = this.__owl__;
|
|
||||||
__owl__.currentUpdateId = __owl__.store._updateId;
|
|
||||||
if (__owl__.ownProps !== nextProps) {
|
|
||||||
__owl__.currentStoreProps = mapStoreToProps(__owl__.store.state, nextProps, __owl__.store.getters);
|
|
||||||
}
|
|
||||||
__owl__.ownProps = nextProps;
|
|
||||||
const mergedProps = Object.assign({}, nextProps, __owl__.currentStoreProps);
|
|
||||||
return super.__updateProps(mergedProps, forceUpdate, patchQueue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// we assign here a unique name to the resulting anonymous class.
|
|
||||||
// this is necessary for Owl to be able to properly deduce templates.
|
|
||||||
// Otherwise, all connected components would have the same name, and then
|
|
||||||
// each component after the first will necessarily have the same template.
|
|
||||||
let name = `Connected${Comp.name}`;
|
|
||||||
Object.defineProperty(Result, "name", { value: name });
|
|
||||||
return Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3602,17 +3808,17 @@
|
|||||||
const utils = _utils;
|
const utils = _utils;
|
||||||
|
|
||||||
exports.Component = Component;
|
exports.Component = Component;
|
||||||
|
exports.ConnectedComponent = ConnectedComponent;
|
||||||
exports.EventBus = EventBus;
|
exports.EventBus = EventBus;
|
||||||
exports.Observer = Observer;
|
exports.Observer = Observer;
|
||||||
exports.QWeb = QWeb;
|
exports.QWeb = QWeb;
|
||||||
exports.Store = Store;
|
exports.Store = Store;
|
||||||
exports.__info__ = __info__;
|
exports.__info__ = __info__;
|
||||||
exports.connect = connect;
|
|
||||||
exports.utils = utils;
|
exports.utils = utils;
|
||||||
|
|
||||||
exports.__info__.version = '0.16.0';
|
exports.__info__.version = '0.17.0';
|
||||||
exports.__info__.date = '2019-06-28T09:20:47.478Z';
|
exports.__info__.date = '2019-07-13T15:13:31.926Z';
|
||||||
exports.__info__.hash = 'd381e85';
|
exports.__info__.hash = '4a90ec7';
|
||||||
exports.__info__.url = 'https://github.com/odoo/owl';
|
exports.__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|
||||||
}(this.owl = this.owl || {}));
|
}(this.owl = this.owl || {}));
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
font-family: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||||
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial,
|
|
||||||
sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -181,10 +179,14 @@ body {
|
|||||||
|
|
||||||
.right-pane .error {
|
.right-pane .error {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-top: 40%;
|
width: 90%;
|
||||||
font-size: 30px;
|
padding-top: 30%;
|
||||||
|
font-size: 18px;
|
||||||
color: darkred;
|
color: darkred;
|
||||||
text-align: center;
|
margin-left: 5%;
|
||||||
padding-left: 30px;
|
}
|
||||||
padding-right: 30px;
|
|
||||||
|
.right-pane .error pre {
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-27
@@ -1,16 +1,17 @@
|
|||||||
const COMPONENTS = `// In this example, we show how components can be defined and created.
|
const COMPONENTS = `// In this example, we show how components can be defined and created.
|
||||||
|
|
||||||
class Counter extends owl.Component {
|
class Greeter extends owl.Component {
|
||||||
state = { value: 0 };
|
state = { word: 'Hello' };
|
||||||
|
|
||||||
increment() {
|
toggle() {
|
||||||
this.state.value++;
|
this.state.word = this.state.word === 'Hi' ? 'Hello' : 'Hi'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main root component
|
// Main root component
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
components = { Counter };
|
components = { Greeter };
|
||||||
|
state = { name: 'World'};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
@@ -21,20 +22,25 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const COMPONENTS_XML = `<templates>
|
const COMPONENTS_XML = `<templates>
|
||||||
<button t-name="Counter" t-on-click="increment">
|
<div t-name="Greeter" class="greeter" t-on-click="toggle">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
<t t-esc="state.word"/>, <t t-esc="props.name"/>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<div t-name="App">
|
<div t-name="App">
|
||||||
<Counter />
|
<Greeter name="state.name"/>
|
||||||
<Counter />
|
|
||||||
</div>
|
</div>
|
||||||
</templates>`;
|
</templates>
|
||||||
|
`;
|
||||||
|
|
||||||
const COMPONENTS_CSS = `button {
|
const COMPONENTS_CSS = `.greeter {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
width: 220px;
|
width: 300px;
|
||||||
|
height: 100px;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
user-select: none;
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const ANIMATION = `// The goal of this component is to see how the t-transition directive can be
|
const ANIMATION = `// The goal of this component is to see how the t-transition directive can be
|
||||||
@@ -399,16 +405,15 @@ class TodoItem extends owl.Component {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// TodoApp
|
// TodoApp
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
function mapStoreToProps(state) {
|
class TodoApp extends owl.ConnectedComponent {
|
||||||
return {
|
|
||||||
todos: state.todos
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class TodoApp extends owl.Component {
|
|
||||||
components = { TodoItem };
|
components = { TodoItem };
|
||||||
state = { filter: "all" };
|
state = { filter: "all" };
|
||||||
|
|
||||||
|
static mapStoreToProps(state) {
|
||||||
|
return {
|
||||||
|
todos: state.todos
|
||||||
|
};
|
||||||
|
}
|
||||||
get visibleTodos() {
|
get visibleTodos() {
|
||||||
let todos = this.props.todos;
|
let todos = this.props.todos;
|
||||||
if (this.state.filter === "active") {
|
if (this.state.filter === "active") {
|
||||||
@@ -456,8 +461,6 @@ class TodoApp extends owl.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectedTodoApp = owl.connect(TodoApp, mapStoreToProps);
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// App Initialization
|
// App Initialization
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -468,7 +471,7 @@ const env = {
|
|||||||
store,
|
store,
|
||||||
dispatch: store.dispatch.bind(store),
|
dispatch: store.dispatch.bind(store),
|
||||||
};
|
};
|
||||||
const app = new ConnectedTodoApp(env);
|
const app = new TodoApp(env);
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -994,17 +997,19 @@ const RESPONSIVE_XML = `<templates>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
|
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
|
||||||
|
<t t-set="maincontent">
|
||||||
|
<FormView />
|
||||||
|
<Chatter />
|
||||||
|
</t>
|
||||||
<Navbar/>
|
<Navbar/>
|
||||||
<ControlPanel/>
|
<ControlPanel/>
|
||||||
<div class="content-wrapper" t-if="!env.isMobile">
|
<div class="content-wrapper" t-if="!env.isMobile">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<FormView />
|
<t t-raw="maincontent"/>
|
||||||
<Chatter />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<t t-else="1">
|
<t t-else="1">
|
||||||
<FormView />
|
<t t-raw="maincontent"/>
|
||||||
<Chatter />
|
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</templates>
|
</templates>
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
<div class="note">Note: these examples require a recent browser to work without a transpilation step. </div>
|
<div class="note">Note: these examples require a recent browser to work without a transpilation step. </div>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="state.error" class="error">
|
<div t-if="state.error" class="error">
|
||||||
<t t-esc="state.error"/>
|
<h3>Error</h3>
|
||||||
|
<pre t-esc="state.error"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" t-ref="content"/>
|
<div class="content" t-ref="content"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user