mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] owl: update to v1.0.0-alpha
This commit is contained in:
@@ -112,10 +112,6 @@
|
||||
const metadata = this.weakMap.get(value);
|
||||
return metadata ? metadata.rev : 0;
|
||||
}
|
||||
deepRevNumber(value) {
|
||||
const metadata = this.weakMap.get(value);
|
||||
return metadata ? metadata.deepRev : 0;
|
||||
}
|
||||
_observe(value, parent) {
|
||||
var self = this;
|
||||
const proxy = new Proxy(value, {
|
||||
@@ -148,7 +144,6 @@
|
||||
value,
|
||||
proxy,
|
||||
rev: this.rev,
|
||||
deepRev: this.rev,
|
||||
parent
|
||||
};
|
||||
this.weakMap.set(value, metadata);
|
||||
@@ -158,11 +153,10 @@
|
||||
_updateRevNumber(target) {
|
||||
this.rev++;
|
||||
let metadata = this.weakMap.get(target);
|
||||
metadata.rev++;
|
||||
let parent = target;
|
||||
do {
|
||||
metadata = this.weakMap.get(parent);
|
||||
metadata.deepRev++;
|
||||
metadata.rev++;
|
||||
} while ((parent = metadata.parent) && parent !== target);
|
||||
}
|
||||
}
|
||||
@@ -1296,6 +1290,7 @@
|
||||
// Const/global stuff/helpers
|
||||
//------------------------------------------------------------------------------
|
||||
const DISABLED_TAGS = ["input", "textarea", "button", "select", "option", "optgroup"];
|
||||
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||
const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
const NODE_HOOKS_PARAMS = {
|
||||
@@ -1357,7 +1352,7 @@
|
||||
// QWeb rendering engine
|
||||
//------------------------------------------------------------------------------
|
||||
class QWeb extends EventBus {
|
||||
constructor(data) {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.h = h;
|
||||
// recursiveTemplates contains sub templates called with t-call, but which
|
||||
@@ -1366,8 +1361,11 @@
|
||||
this.recursiveFns = {};
|
||||
this.isUpdating = false;
|
||||
this.templates = Object.create(QWeb.TEMPLATES);
|
||||
if (data) {
|
||||
this.addTemplates(data);
|
||||
if (config.templates) {
|
||||
this.addTemplates(config.templates);
|
||||
}
|
||||
if (config.translateFn) {
|
||||
this.translateFn = config.translateFn;
|
||||
}
|
||||
}
|
||||
static addDirective(directive) {
|
||||
@@ -1594,6 +1592,11 @@
|
||||
}
|
||||
text = text.replace(whitespaceRE, " ");
|
||||
}
|
||||
if (this.translateFn) {
|
||||
if (node.parentNode.getAttribute("t-translation") !== "off") {
|
||||
text = this.translateFn(text);
|
||||
}
|
||||
}
|
||||
if (ctx.parentNode) {
|
||||
if (node.nodeType === 3) {
|
||||
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
||||
@@ -1771,7 +1774,10 @@
|
||||
let classObj = "";
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
let name = attributes[i].name;
|
||||
const value = attributes[i].textContent;
|
||||
let value = attributes[i].textContent;
|
||||
if (this.translateFn && TRANSLATABLE_ATTRS.includes(name)) {
|
||||
value = this.translateFn(value);
|
||||
}
|
||||
// regular attributes
|
||||
if (!name.startsWith("t-") && !node.getAttribute("t-attf-" + name)) {
|
||||
const attID = ctx.generateID();
|
||||
@@ -1856,15 +1862,7 @@
|
||||
}
|
||||
}
|
||||
let nodeID = ctx.generateID();
|
||||
let nodeKey = node.getAttribute("t-key");
|
||||
if (nodeKey) {
|
||||
ctx.addLine(`const nodeKey${nodeID} = ${ctx.formatExpression(nodeKey)}`);
|
||||
nodeKey = `nodeKey${nodeID}`;
|
||||
ctx.lastNodeKey = nodeKey;
|
||||
}
|
||||
else {
|
||||
nodeKey = nodeID;
|
||||
}
|
||||
let nodeKey = ctx.lastNodeKey || nodeID;
|
||||
const parts = [`key:${nodeKey}`];
|
||||
if (attrs.length + tattrs.length > 0) {
|
||||
parts.push(`attrs:{${attrs.join(",")}}`);
|
||||
@@ -1910,7 +1908,7 @@
|
||||
name: 1,
|
||||
att: 1,
|
||||
attf: 1,
|
||||
key: 1
|
||||
translation: 1
|
||||
};
|
||||
QWeb.DIRECTIVES = [];
|
||||
QWeb.TEMPLATES = {};
|
||||
@@ -2224,18 +2222,10 @@
|
||||
ctx.addToScope(name, `_${keysID}[${loopVar}]`);
|
||||
ctx.addToScope(name + "_value", `_${valuesID}[${loopVar}]`);
|
||||
const nodeCopy = node.cloneNode(true);
|
||||
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
|
||||
if (!shouldWarn && node.tagName === "t") {
|
||||
if (node.hasAttribute("t-component") && !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;
|
||||
}
|
||||
}
|
||||
let shouldWarn = !nodeCopy.hasAttribute("t-key") &&
|
||||
node.children.length === 1 &&
|
||||
node.children[0].tagName !== "t" &&
|
||||
!node.children[0].hasAttribute("t-key");
|
||||
if (shouldWarn) {
|
||||
console.warn(`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')`);
|
||||
}
|
||||
@@ -2505,6 +2495,50 @@
|
||||
ctx.addLine(`p${nodeID}.on['${event}'] = extra.handlers['${event}' + ${nodeID}];`);
|
||||
}
|
||||
});
|
||||
//------------------------------------------------------------------------------
|
||||
// t-key
|
||||
//------------------------------------------------------------------------------
|
||||
QWeb.addDirective({
|
||||
name: "key",
|
||||
priority: 45,
|
||||
atNodeEncounter({ ctx, value }) {
|
||||
let id = ctx.generateID();
|
||||
ctx.addLine(`const nodeKey${id} = ${ctx.formatExpression(value)};`);
|
||||
ctx.lastNodeKey = `nodeKey${id}`;
|
||||
}
|
||||
});
|
||||
|
||||
const config = {};
|
||||
Object.defineProperty(config, "mode", {
|
||||
get() {
|
||||
return QWeb.dev ? "dev" : "prod";
|
||||
},
|
||||
set(mode) {
|
||||
QWeb.dev = mode === "dev";
|
||||
if (QWeb.dev) {
|
||||
const url = `https://github.com/odoo/owl/blob/master/doc/tooling.md#development-mode`;
|
||||
console.warn(`Owl is running in 'dev' mode. This is not suitable for production use. See ${url} for more information.`);
|
||||
}
|
||||
else {
|
||||
console.log(`Owl is now running in 'prod' mode.`);
|
||||
}
|
||||
},
|
||||
});
|
||||
let env;
|
||||
Object.defineProperty(config, "env", {
|
||||
get() {
|
||||
if (!env) {
|
||||
env = {};
|
||||
}
|
||||
if (!env.qweb) {
|
||||
env.qweb = new QWeb();
|
||||
}
|
||||
return env;
|
||||
},
|
||||
set(newEnv) {
|
||||
env = newEnv;
|
||||
},
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// t-component
|
||||
@@ -2686,7 +2720,7 @@
|
||||
*/
|
||||
QWeb.addDirective({
|
||||
name: "component",
|
||||
extraNames: ["props", "keepalive"],
|
||||
extraNames: ["props"],
|
||||
priority: 100,
|
||||
atNodeEncounter({ ctx, value, node, qweb }) {
|
||||
ctx.addLine("//COMPONENT");
|
||||
@@ -2694,7 +2728,6 @@
|
||||
ctx.rootContext.shouldDefineQWeb = true;
|
||||
ctx.rootContext.shouldDefineParent = true;
|
||||
ctx.rootContext.shouldDefineUtils = true;
|
||||
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
||||
let hasDynamicProps = node.getAttribute("t-props") ? true : false;
|
||||
// t-on- events and t-transition
|
||||
const events = [];
|
||||
@@ -2723,28 +2756,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
let key = node.getAttribute("t-key");
|
||||
if (key) {
|
||||
key = ctx.formatExpression(key);
|
||||
}
|
||||
// computing the props string representing the props object
|
||||
let propStr = Object.keys(props)
|
||||
.map(k => k + ":" + props[k])
|
||||
.join(",");
|
||||
let defID = ctx.generateID();
|
||||
let componentID = ctx.generateID();
|
||||
let keyID = key && ctx.generateID();
|
||||
if (key) {
|
||||
// we bind a variable to the key (could be a complex expression, so we
|
||||
// want to evaluate it only once)
|
||||
ctx.addLine(`let key${keyID} = 'key' + ${key};`);
|
||||
}
|
||||
let locationExpr = `\`__${ctx.generateID()}__`;
|
||||
for (let i = 0; i < ctx.loopNumber - 1; i++) {
|
||||
locationExpr += `\${i${i + 1}}__`;
|
||||
}
|
||||
if (key || ctx.currentKey) {
|
||||
const k = key ? `key${keyID}` : ctx.currentKey;
|
||||
if (ctx.lastNodeKey || ctx.currentKey) {
|
||||
const k = ctx.lastNodeKey || ctx.currentKey;
|
||||
ctx.addLine(`let templateId${componentID} = ${locationExpr}\` + ${k};`);
|
||||
}
|
||||
else {
|
||||
@@ -2765,8 +2788,8 @@
|
||||
if (transition) {
|
||||
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
|
||||
}
|
||||
let finalizeComponentCode = `w${componentID}.${keepAlive ? "unmount" : "destroy"}();`;
|
||||
if (ref && !keepAlive) {
|
||||
let finalizeComponentCode = `w${componentID}.destroy();`;
|
||||
if (ref) {
|
||||
finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`;
|
||||
}
|
||||
if (transition) {
|
||||
@@ -2845,9 +2868,6 @@
|
||||
}
|
||||
ctx.addLine(`let w${componentID} = ${templateId} in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[${templateId}]] : false;`);
|
||||
let shouldProxy = !ctx.parentNode;
|
||||
if (keepAlive) {
|
||||
ctx.addLine(`const fiber${componentID} = Object.assign(Object.create(extra.fiber), {patchQueue: []});`);
|
||||
}
|
||||
if (shouldProxy) {
|
||||
let id = ctx.generateID();
|
||||
ctx.rootContext.rootNode = id;
|
||||
@@ -2893,29 +2913,13 @@
|
||||
}
|
||||
ctx.addIf(`w${componentID}`);
|
||||
// need to update component
|
||||
let patchQueueCode = keepAlive ? `fiber${componentID}` : "extra.fiber";
|
||||
if (keepAlive) {
|
||||
// if we have t-keepalive="1", the component could be unmounted, but then
|
||||
// we __updateProps is called. This is ok, but we do not want to call
|
||||
// the willPatch/patched hooks of the component in this case, so we
|
||||
// disable the patch queue
|
||||
patchQueueCode = `w${componentID}.__owl__.isMounted ? extra.fiber : fiber${componentID}`;
|
||||
}
|
||||
if (QWeb.dev) {
|
||||
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
|
||||
}
|
||||
let styleCode = "";
|
||||
if (tattStyle) {
|
||||
styleCode = `.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};w${componentID}.el.style=${tattStyle};});`;
|
||||
}
|
||||
ctx.addLine(`w${componentID}.__updateProps(props${componentID}, ${patchQueueCode}${scopeVars &&
|
||||
ctx.addLine(`w${componentID}.__updateProps(props${componentID}, extra.fiber${scopeVars &&
|
||||
", " + scopeVars}, sibling)${styleCode};`);
|
||||
ctx.addLine(`let pvnode = w${componentID}.__owl__.pvnode;`);
|
||||
let keepAliveCode = "";
|
||||
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();};`;
|
||||
ctx.addLine(keepAliveCode);
|
||||
}
|
||||
if (registerCode) {
|
||||
ctx.addLine(registerCode);
|
||||
}
|
||||
@@ -2980,6 +2984,54 @@
|
||||
}
|
||||
});
|
||||
|
||||
class Scheduler {
|
||||
constructor(requestAnimationFrame) {
|
||||
this.tasks = [];
|
||||
this.isRunning = false;
|
||||
this.requestAnimationFrame = requestAnimationFrame;
|
||||
}
|
||||
addFiber(fiber, callback) {
|
||||
this.tasks.push({ fiber, callback });
|
||||
if (this.isRunning) {
|
||||
return;
|
||||
}
|
||||
this.scheduleTasks();
|
||||
}
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
*/
|
||||
flush() {
|
||||
let tasks = this.tasks;
|
||||
this.tasks = [];
|
||||
tasks = tasks.filter(task => {
|
||||
if (task.fiber.isCancelled) {
|
||||
return false;
|
||||
}
|
||||
if (task.fiber.counter === 0) {
|
||||
task.callback(task.fiber.error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.tasks = tasks.concat(this.tasks);
|
||||
}
|
||||
scheduleTasks() {
|
||||
this.isRunning = true;
|
||||
this.requestAnimationFrame(() => {
|
||||
this.flush();
|
||||
if (this.tasks.length > 0) {
|
||||
this.scheduleTasks();
|
||||
}
|
||||
else {
|
||||
this.isRunning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const raf = window.requestAnimationFrame.bind(window);
|
||||
const scheduler = new Scheduler(raf);
|
||||
|
||||
/**
|
||||
* Owl Fiber Class
|
||||
*
|
||||
@@ -2993,7 +3045,7 @@
|
||||
* states and in general determine the state of the rendering.
|
||||
*/
|
||||
class Fiber {
|
||||
constructor(parent, component, props, scope, vars, force) {
|
||||
constructor(parent, component, scope, vars, force) {
|
||||
// isCancelled means that the rendering corresponding to this fiber and its
|
||||
// children is cancelled. No extra work should be done.
|
||||
this.isCancelled = false;
|
||||
@@ -3019,7 +3071,6 @@
|
||||
this.force = force;
|
||||
this.scope = scope;
|
||||
this.vars = vars;
|
||||
this.props = props;
|
||||
this.component = component;
|
||||
this.root = parent ? parent.root : this;
|
||||
this.parent = parent;
|
||||
@@ -3102,7 +3153,6 @@
|
||||
};
|
||||
this._walk(doWork);
|
||||
let component = this.component;
|
||||
this.shouldPatch = false;
|
||||
const patchLen = patchQueue.length;
|
||||
try {
|
||||
for (let i = 0; i < patchLen; i++) {
|
||||
@@ -3116,15 +3166,10 @@
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
try {
|
||||
for (let i = 0; i < patchLen; i++) {
|
||||
const fiber = patchQueue[i];
|
||||
component = fiber.component;
|
||||
component.__patch(fiber.vnode);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.handleError(e);
|
||||
for (let i = 0; i < patchLen; i++) {
|
||||
const fiber = patchQueue[i];
|
||||
component = fiber.component;
|
||||
component.__patch(fiber.vnode);
|
||||
}
|
||||
try {
|
||||
for (let i = patchLen - 1; i >= 0; i--) {
|
||||
@@ -3138,7 +3183,6 @@
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.shouldPatch = true;
|
||||
}
|
||||
/**
|
||||
* Cancel a fiber and all its children.
|
||||
@@ -3152,7 +3196,39 @@
|
||||
return f.child;
|
||||
});
|
||||
}
|
||||
handleError(e) { }
|
||||
/**
|
||||
* This is the global error handler for errors occurring in Owl main lifecycle
|
||||
* methods. Caught errors are triggered on the QWeb instance, and are
|
||||
* potentially given to some parent component which implements `catchError`.
|
||||
*
|
||||
* If there are no such component, we destroy everything. This is better than
|
||||
* being in a corrupted state.
|
||||
*/
|
||||
handleError(error) {
|
||||
let canCatch = false;
|
||||
let component = this.component;
|
||||
let qweb = component.env.qweb;
|
||||
let root = component;
|
||||
while (component && !(canCatch = !!component.catchError)) {
|
||||
root = component;
|
||||
component = component.__owl__.parent;
|
||||
}
|
||||
qweb.trigger("error", error);
|
||||
if (canCatch) {
|
||||
setTimeout(() => {
|
||||
console.error(error);
|
||||
component.catchError(error);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// the 3 next lines aim to mark the root fiber as being in error, and
|
||||
// to force it to end, without waiting for its children
|
||||
this.root.counter = 0;
|
||||
this.root.error = error;
|
||||
scheduler.flush();
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -3174,7 +3250,7 @@
|
||||
// optional prop
|
||||
break;
|
||||
}
|
||||
if (!props[propName]) {
|
||||
if (!(propName in props)) {
|
||||
throw new Error(`Missing props '${propsDef[i]}' (component '${Widget.name}')`);
|
||||
}
|
||||
}
|
||||
@@ -3250,67 +3326,6 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
class Scheduler {
|
||||
constructor(requestAnimationFrame) {
|
||||
this.tasks = [];
|
||||
this.isRunning = false;
|
||||
this.requestAnimationFrame = requestAnimationFrame;
|
||||
}
|
||||
addFiber(fiber, callback) {
|
||||
this.tasks.push({ fiber, callback });
|
||||
if (this.isRunning) {
|
||||
return;
|
||||
}
|
||||
this.scheduleTasks();
|
||||
}
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
*/
|
||||
flush() {
|
||||
let tasks = this.tasks;
|
||||
this.tasks = [];
|
||||
tasks = tasks.filter(task => {
|
||||
if (task.fiber.isCancelled) {
|
||||
return false;
|
||||
}
|
||||
if (task.fiber.counter === 0) {
|
||||
task.callback();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.tasks = tasks.concat(this.tasks);
|
||||
}
|
||||
scheduleTasks() {
|
||||
this.isRunning = true;
|
||||
this.requestAnimationFrame(() => {
|
||||
this.flush();
|
||||
if (this.tasks.length > 0) {
|
||||
this.scheduleTasks();
|
||||
}
|
||||
else {
|
||||
this.isRunning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owl Component System
|
||||
*
|
||||
* This file introduces a declarative and composable component system. It
|
||||
* contains:
|
||||
*
|
||||
* - the Env interface (generic type for the environment)
|
||||
* - the Internal interface (the owl specific metadata attached to a component)
|
||||
* - the Component class
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
// Types/helpers
|
||||
//------------------------------------------------------------------------------
|
||||
const raf = window.requestAnimationFrame.bind(window);
|
||||
const scheduler = new Scheduler(raf);
|
||||
//------------------------------------------------------------------------------
|
||||
// Component
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -3322,47 +3337,32 @@
|
||||
/**
|
||||
* Creates an instance of Component.
|
||||
*
|
||||
* The root component of a component tree needs an environment:
|
||||
*
|
||||
* ```javascript
|
||||
* const root = new RootComponent(env, props);
|
||||
* ```
|
||||
*
|
||||
* Every other component simply needs a reference to its parent:
|
||||
*
|
||||
* ```javascript
|
||||
* const child = new SomeComponent(parent, props);
|
||||
* ```
|
||||
*
|
||||
* Note that most of the time, only the root component needs to be created by
|
||||
* hand. Other components should be created automatically by the framework (with
|
||||
* the t-component directive in a template)
|
||||
*/
|
||||
constructor(parent, props) {
|
||||
const defaultProps = this.constructor.defaultProps;
|
||||
Component.current = this;
|
||||
if (defaultProps) {
|
||||
props = this.__applyDefaultProps(props, defaultProps);
|
||||
}
|
||||
// is this a good idea?
|
||||
// Pro: if props is empty, we can create easily a component
|
||||
// Con: this is not really safe
|
||||
// Pro: but creating component (by a template) is always unsafe anyway
|
||||
this.props = props || {};
|
||||
let id = nextId++;
|
||||
let p = null;
|
||||
if (parent instanceof Component) {
|
||||
p = parent;
|
||||
this.env = parent.env;
|
||||
parent.__owl__.children[id] = this;
|
||||
}
|
||||
else {
|
||||
this.env = parent;
|
||||
const id = nextId++;
|
||||
let depth;
|
||||
if (parent) {
|
||||
const defaultProps = this.constructor.defaultProps;
|
||||
if (defaultProps) {
|
||||
props = this.__applyDefaultProps(props, defaultProps);
|
||||
}
|
||||
this.props = props;
|
||||
if (QWeb.dev) {
|
||||
// we only validate props for root widgets here. "Regular" widget
|
||||
// props are validated by the t-component directive
|
||||
QWeb.utils.validateProps(this.constructor, this.props);
|
||||
}
|
||||
this.env = parent.env;
|
||||
const __powl__ = parent.__owl__;
|
||||
__powl__.children[id] = this;
|
||||
depth = __powl__.depth + 1;
|
||||
}
|
||||
else {
|
||||
// we are the root component
|
||||
this.env = config.env;
|
||||
this.props = undefined;
|
||||
this.env.qweb.on("update", this, () => {
|
||||
if (this.__owl__.isMounted) {
|
||||
this.render(true);
|
||||
@@ -3376,15 +3376,17 @@
|
||||
this.env.qweb.off("update", this);
|
||||
}
|
||||
});
|
||||
depth = 0;
|
||||
}
|
||||
const qweb = this.env.qweb;
|
||||
this.__owl__ = {
|
||||
id: id,
|
||||
depth: depth,
|
||||
vnode: null,
|
||||
pvnode: null,
|
||||
isMounted: false,
|
||||
isDestroyed: false,
|
||||
parent: p,
|
||||
parent: parent || null,
|
||||
children: {},
|
||||
cmap: {},
|
||||
currentFiber: null,
|
||||
@@ -3474,11 +3476,6 @@
|
||||
* @see mounted
|
||||
*/
|
||||
willUnmount() { }
|
||||
/**
|
||||
* catchError is a method called whenever some error happens in the rendering or
|
||||
* lifecycle hooks of a child.
|
||||
*/
|
||||
catchError(error) { }
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -3502,15 +3499,13 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
const fiber = new Fiber(null, this, this.props, undefined, undefined, false);
|
||||
if (!__owl__.vnode) {
|
||||
this.__prepareAndRender(fiber);
|
||||
}
|
||||
else {
|
||||
this.__render(fiber);
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
scheduler.addFiber(fiber, () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fiber = new Fiber(null, this, undefined, undefined, false);
|
||||
scheduler.addFiber(fiber, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (!__owl__.isDestroyed) {
|
||||
this.__patch(fiber.vnode);
|
||||
target.appendChild(this.el);
|
||||
@@ -3520,6 +3515,12 @@
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
if (!__owl__.vnode) {
|
||||
this.__prepareAndRender(fiber);
|
||||
}
|
||||
else {
|
||||
this.__render(fiber);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
@@ -3547,15 +3548,19 @@
|
||||
(__owl__.currentFiber && !__owl__.currentFiber.isRendered)) {
|
||||
return;
|
||||
}
|
||||
const fiber = new Fiber(null, this, this.props, undefined, undefined, force);
|
||||
this.__render(fiber);
|
||||
return new Promise(resolve => {
|
||||
scheduler.addFiber(fiber.root, () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fiber = new Fiber(null, this, undefined, undefined, force);
|
||||
scheduler.addFiber(fiber.root, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (__owl__.isMounted && fiber === fiber.root) {
|
||||
fiber.patchComponents();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
this.__render(fiber);
|
||||
});
|
||||
}
|
||||
/**
|
||||
@@ -3680,7 +3685,7 @@
|
||||
const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps);
|
||||
if (shouldUpdate) {
|
||||
const __owl__ = this.__owl__;
|
||||
const fiber = new Fiber(parentFiber, this, this.props, scope, vars, parentFiber.force);
|
||||
const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force);
|
||||
if (!parentFiber.child) {
|
||||
parentFiber.child = fiber;
|
||||
}
|
||||
@@ -3691,6 +3696,9 @@
|
||||
if (defaultProps) {
|
||||
nextProps = this.__applyDefaultProps(nextProps, defaultProps);
|
||||
}
|
||||
if (QWeb.dev) {
|
||||
QWeb.utils.validateProps(this.constructor, nextProps);
|
||||
}
|
||||
await Promise.all([
|
||||
this.willUpdateProps(nextProps),
|
||||
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps)
|
||||
@@ -3718,7 +3726,7 @@
|
||||
* parent template.
|
||||
*/
|
||||
__prepare(parentFiber, scope, vars, previousSibling) {
|
||||
const fiber = new Fiber(parentFiber, this, this.props, scope, vars, parentFiber.force);
|
||||
const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force);
|
||||
fiber.shouldPatch = false;
|
||||
if (!parentFiber.child) {
|
||||
parentFiber.child = fiber;
|
||||
@@ -3757,7 +3765,7 @@
|
||||
await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]);
|
||||
}
|
||||
catch (e) {
|
||||
errorHandler(e, fiber);
|
||||
fiber.handleError(e);
|
||||
fiber.vnode = h("div"); // -> we render this div at the end
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -3782,7 +3790,7 @@
|
||||
}
|
||||
catch (e) {
|
||||
vnode = __owl__.vnode || h("div");
|
||||
errorHandler(e, fiber);
|
||||
fiber.handleError(e);
|
||||
}
|
||||
fiber.vnode = vnode;
|
||||
if (__owl__.observer) {
|
||||
@@ -3846,40 +3854,6 @@
|
||||
Component._template = null;
|
||||
Component.current = null;
|
||||
Component.components = {};
|
||||
//------------------------------------------------------------------------------
|
||||
// Error handling
|
||||
//------------------------------------------------------------------------------
|
||||
Fiber.prototype.handleError = function (error) {
|
||||
errorHandler(error, this);
|
||||
};
|
||||
/**
|
||||
* This is the global error handler for errors occurring in Owl main lifecycle
|
||||
* methods. Caught errors are triggered on the QWeb instance, and are
|
||||
* potentially given to some parent component which implements `catchError`.
|
||||
*
|
||||
* If there are no such component, we destroy everything. This is better than
|
||||
* being in a corrupted state.
|
||||
*/
|
||||
function errorHandler(error, fiber) {
|
||||
let canCatch = false;
|
||||
let component = fiber.component;
|
||||
let qweb = component.env.qweb;
|
||||
let root = component;
|
||||
while (component && !(canCatch = component.catchError !== Component.prototype.catchError)) {
|
||||
root = component;
|
||||
component = component.__owl__.parent;
|
||||
}
|
||||
console.error(error);
|
||||
qweb.trigger("error", error);
|
||||
if (canCatch) {
|
||||
setTimeout(() => {
|
||||
component.catchError(error);
|
||||
});
|
||||
}
|
||||
else {
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owl Hook System
|
||||
@@ -4011,6 +3985,27 @@
|
||||
* With a `Context` object, each component can subscribe (with the `useContext`
|
||||
* hook) to its state, and will be updated whenever the context state is updated.
|
||||
*/
|
||||
function partitionBy(arr, fn) {
|
||||
let lastGroup = false;
|
||||
let lastValue;
|
||||
return arr.reduce((acc, cur) => {
|
||||
let curVal = fn(cur);
|
||||
if (lastGroup) {
|
||||
if (curVal === lastValue) {
|
||||
lastGroup.push(cur);
|
||||
}
|
||||
else {
|
||||
lastGroup = false;
|
||||
}
|
||||
}
|
||||
if (!lastGroup) {
|
||||
lastGroup = [cur];
|
||||
acc.push(lastGroup);
|
||||
}
|
||||
lastValue = curVal;
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
class Context extends EventBus {
|
||||
constructor(state = {}) {
|
||||
super();
|
||||
@@ -4020,37 +4015,44 @@
|
||||
this.observer = new Observer();
|
||||
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
||||
this.state = this.observer.observe(state);
|
||||
this.subscriptions.update = [];
|
||||
}
|
||||
/**
|
||||
* Instead of using trigger to emit an update event, we actually implement
|
||||
* our own function to do that. The reason is that we need to be smarter than
|
||||
* a simple trigger function: we need to wait for parent components to be
|
||||
* done before doing children components. The reason is that if an update
|
||||
* as an effect of destroying a children, we do not want to call the
|
||||
* mapStoreToProps function of the child, nor rendering it.
|
||||
* done before doing children components. More precisely, if an update
|
||||
* as an effect of destroying a children, we do not want to call any code
|
||||
* from the child, and certainly not render it.
|
||||
*
|
||||
* This method is not optimal if we have a bunch of asynchronous components:
|
||||
* we wait sequentially for each component to be completed before updating the
|
||||
* next. However, the only things that matters is that children are updated
|
||||
* after their parents. So, this could be optimized by being smarter, and
|
||||
* updating all widgets concurrently, except for parents/children.
|
||||
* This method implements a simple grouping algorithm by depth. If we have
|
||||
* connected components of depths [2, 4,4,4,4, 3,8,8], the Context will notify
|
||||
* them in the following groups: [2], [4,4,4,4], [3], [8,8]. Each group will
|
||||
* be updated sequentially, but each components in a given group will be done in
|
||||
* parallel.
|
||||
*
|
||||
* A potential cheap way to improve this situation is to keep track of the
|
||||
* depth of a component in the component tree. A root component has a depth of
|
||||
* 1, then its children of 2 and so on... Then, we can update all components
|
||||
* with the same depth in parallel.
|
||||
* This is a very simple algorithm, but it avoids checking if a given
|
||||
* component is a child of another.
|
||||
*/
|
||||
async __notifyComponents() {
|
||||
const rev = ++this.rev;
|
||||
const subs = this.subscriptions.update || [];
|
||||
for (let i = 0, iLen = subs.length; i < iLen; i++) {
|
||||
const sub = subs[i];
|
||||
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
|
||||
if (shouldCallback) {
|
||||
const render = sub.callback.call(sub.owner, rev);
|
||||
scheduler.flush();
|
||||
await render;
|
||||
}
|
||||
const subscriptions = this.subscriptions.update;
|
||||
const groups = partitionBy(subscriptions, s => (s.owner ? s.owner.__owl__.depth : -1));
|
||||
for (let group of groups) {
|
||||
const proms = Promise.all(group.map(sub => {
|
||||
if (sub.owner ? sub.owner.__owl__.isMounted : true) {
|
||||
return sub.callback.call(sub.owner, rev);
|
||||
}
|
||||
}));
|
||||
// at this point, each component in the current group has registered a
|
||||
// top level fiber in the scheduler. It could happen that rendering these
|
||||
// components is done (if they have no children). This is why we manually
|
||||
// flush the scheduler. This will force the scheduler to check
|
||||
// immediately if they are done, which will cause their rendering
|
||||
// promise to resolve earlier, which means that there is a chance of
|
||||
// processing the next group in the same frame.
|
||||
scheduler.flush();
|
||||
await proms;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4137,7 +4139,7 @@
|
||||
const component = Component.current;
|
||||
const store = options.store || component.env.store;
|
||||
let result = selector(store.state, component.props);
|
||||
const hashFn = store.observer.deepRevNumber.bind(store.observer);
|
||||
const hashFn = store.observer.revNumber.bind(store.observer);
|
||||
let revNumber = hashFn(result) || result;
|
||||
const isEqual = options.isEqual || isStrictEqual;
|
||||
if (!store.updateFunctions[component.__owl__.id]) {
|
||||
@@ -4165,7 +4167,6 @@
|
||||
}
|
||||
});
|
||||
onWillUpdateProps(props => {
|
||||
// FIXME: only do that if not keepalive + do it in destroy in that case
|
||||
delete store.updateFunctions[component.__owl__.id];
|
||||
result = selector(store.state, props);
|
||||
});
|
||||
@@ -4373,7 +4374,7 @@
|
||||
// Private helpers
|
||||
//--------------------------------------------------------------------------
|
||||
setUrlFromPath(path) {
|
||||
const separator = this.mode === "hash" ? "/" : "";
|
||||
const separator = this.mode === "hash" ? location.pathname : "";
|
||||
const url = location.origin + separator + path;
|
||||
if (url !== window.location.href) {
|
||||
window.history.pushState({}, path, url);
|
||||
@@ -4513,27 +4514,13 @@
|
||||
useStore: useStore
|
||||
});
|
||||
const __info__ = {};
|
||||
Object.defineProperty(__info__, "mode", {
|
||||
get() {
|
||||
return QWeb.dev ? "dev" : "prod";
|
||||
},
|
||||
set(mode) {
|
||||
QWeb.dev = mode === "dev";
|
||||
if (QWeb.dev) {
|
||||
const url = `https://github.com/odoo/owl/blob/master/doc/tooling.md#development-mode`;
|
||||
console.warn(`Owl is running in 'dev' mode. This is not suitable for production use. See ${url} for more information.`);
|
||||
}
|
||||
else {
|
||||
console.log(`Owl is now running in 'prod' mode.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.Component = Component;
|
||||
exports.Context = Context$1;
|
||||
exports.QWeb = QWeb;
|
||||
exports.Store = Store$1;
|
||||
exports.__info__ = __info__;
|
||||
exports.config = config;
|
||||
exports.core = core;
|
||||
exports.hooks = hooks$1;
|
||||
exports.misc = misc;
|
||||
@@ -4542,9 +4529,9 @@
|
||||
exports.useState = useState$1;
|
||||
exports.utils = utils;
|
||||
|
||||
exports.__info__.version = '0.24.0';
|
||||
exports.__info__.date = '2019-10-25T15:08:12.840Z';
|
||||
exports.__info__.hash = 'f0b5a55';
|
||||
exports.__info__.version = '1.0.0-alpha';
|
||||
exports.__info__.date = '2019-10-30T15:41:04.324Z';
|
||||
exports.__info__.hash = '2fc71cf';
|
||||
exports.__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
}(this.owl = this.owl || {}));
|
||||
|
||||
Reference in New Issue
Block a user