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:
+1
-2
@@ -63,7 +63,7 @@ class Counter extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const counter = new Counter({ qweb: new QWeb(TEMPLATES) });
|
const counter = new Counter();
|
||||||
counter.mount(document.body);</code></pre>
|
counter.mount(document.body);</code></pre>
|
||||||
</section>
|
</section>
|
||||||
<section class="column">
|
<section class="column">
|
||||||
@@ -74,7 +74,6 @@ counter.mount(document.body);</code></pre>
|
|||||||
</templates></code></pre>
|
</templates></code></pre>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<p>Note that we assume here that the xml templates are available in the <tt>TEMPLATES</tt> string. More interesting examples can be found on the <a href="playground/">Playground</a> application.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -112,10 +112,6 @@
|
|||||||
const metadata = this.weakMap.get(value);
|
const metadata = this.weakMap.get(value);
|
||||||
return metadata ? metadata.rev : 0;
|
return metadata ? metadata.rev : 0;
|
||||||
}
|
}
|
||||||
deepRevNumber(value) {
|
|
||||||
const metadata = this.weakMap.get(value);
|
|
||||||
return metadata ? metadata.deepRev : 0;
|
|
||||||
}
|
|
||||||
_observe(value, parent) {
|
_observe(value, parent) {
|
||||||
var self = this;
|
var self = this;
|
||||||
const proxy = new Proxy(value, {
|
const proxy = new Proxy(value, {
|
||||||
@@ -148,7 +144,6 @@
|
|||||||
value,
|
value,
|
||||||
proxy,
|
proxy,
|
||||||
rev: this.rev,
|
rev: this.rev,
|
||||||
deepRev: this.rev,
|
|
||||||
parent
|
parent
|
||||||
};
|
};
|
||||||
this.weakMap.set(value, metadata);
|
this.weakMap.set(value, metadata);
|
||||||
@@ -158,11 +153,10 @@
|
|||||||
_updateRevNumber(target) {
|
_updateRevNumber(target) {
|
||||||
this.rev++;
|
this.rev++;
|
||||||
let metadata = this.weakMap.get(target);
|
let metadata = this.weakMap.get(target);
|
||||||
metadata.rev++;
|
|
||||||
let parent = target;
|
let parent = target;
|
||||||
do {
|
do {
|
||||||
metadata = this.weakMap.get(parent);
|
metadata = this.weakMap.get(parent);
|
||||||
metadata.deepRev++;
|
metadata.rev++;
|
||||||
} while ((parent = metadata.parent) && parent !== target);
|
} while ((parent = metadata.parent) && parent !== target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1296,6 +1290,7 @@
|
|||||||
// Const/global stuff/helpers
|
// Const/global stuff/helpers
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
const DISABLED_TAGS = ["input", "textarea", "button", "select", "option", "optgroup"];
|
const DISABLED_TAGS = ["input", "textarea", "button", "select", "option", "optgroup"];
|
||||||
|
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||||
const lineBreakRE = /[\r\n]/;
|
const lineBreakRE = /[\r\n]/;
|
||||||
const whitespaceRE = /\s+/g;
|
const whitespaceRE = /\s+/g;
|
||||||
const NODE_HOOKS_PARAMS = {
|
const NODE_HOOKS_PARAMS = {
|
||||||
@@ -1357,7 +1352,7 @@
|
|||||||
// QWeb rendering engine
|
// QWeb rendering engine
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class QWeb extends EventBus {
|
class QWeb extends EventBus {
|
||||||
constructor(data) {
|
constructor(config = {}) {
|
||||||
super();
|
super();
|
||||||
this.h = h;
|
this.h = h;
|
||||||
// recursiveTemplates contains sub templates called with t-call, but which
|
// recursiveTemplates contains sub templates called with t-call, but which
|
||||||
@@ -1366,8 +1361,11 @@
|
|||||||
this.recursiveFns = {};
|
this.recursiveFns = {};
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
this.templates = Object.create(QWeb.TEMPLATES);
|
this.templates = Object.create(QWeb.TEMPLATES);
|
||||||
if (data) {
|
if (config.templates) {
|
||||||
this.addTemplates(data);
|
this.addTemplates(config.templates);
|
||||||
|
}
|
||||||
|
if (config.translateFn) {
|
||||||
|
this.translateFn = config.translateFn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static addDirective(directive) {
|
static addDirective(directive) {
|
||||||
@@ -1594,6 +1592,11 @@
|
|||||||
}
|
}
|
||||||
text = text.replace(whitespaceRE, " ");
|
text = text.replace(whitespaceRE, " ");
|
||||||
}
|
}
|
||||||
|
if (this.translateFn) {
|
||||||
|
if (node.parentNode.getAttribute("t-translation") !== "off") {
|
||||||
|
text = this.translateFn(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ctx.parentNode) {
|
if (ctx.parentNode) {
|
||||||
if (node.nodeType === 3) {
|
if (node.nodeType === 3) {
|
||||||
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
||||||
@@ -1771,7 +1774,10 @@
|
|||||||
let classObj = "";
|
let classObj = "";
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
let name = attributes[i].name;
|
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
|
// regular attributes
|
||||||
if (!name.startsWith("t-") && !node.getAttribute("t-attf-" + name)) {
|
if (!name.startsWith("t-") && !node.getAttribute("t-attf-" + name)) {
|
||||||
const attID = ctx.generateID();
|
const attID = ctx.generateID();
|
||||||
@@ -1856,15 +1862,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let nodeID = ctx.generateID();
|
let nodeID = ctx.generateID();
|
||||||
let nodeKey = node.getAttribute("t-key");
|
let nodeKey = ctx.lastNodeKey || nodeID;
|
||||||
if (nodeKey) {
|
|
||||||
ctx.addLine(`const nodeKey${nodeID} = ${ctx.formatExpression(nodeKey)}`);
|
|
||||||
nodeKey = `nodeKey${nodeID}`;
|
|
||||||
ctx.lastNodeKey = nodeKey;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nodeKey = nodeID;
|
|
||||||
}
|
|
||||||
const parts = [`key:${nodeKey}`];
|
const parts = [`key:${nodeKey}`];
|
||||||
if (attrs.length + tattrs.length > 0) {
|
if (attrs.length + tattrs.length > 0) {
|
||||||
parts.push(`attrs:{${attrs.join(",")}}`);
|
parts.push(`attrs:{${attrs.join(",")}}`);
|
||||||
@@ -1910,7 +1908,7 @@
|
|||||||
name: 1,
|
name: 1,
|
||||||
att: 1,
|
att: 1,
|
||||||
attf: 1,
|
attf: 1,
|
||||||
key: 1
|
translation: 1
|
||||||
};
|
};
|
||||||
QWeb.DIRECTIVES = [];
|
QWeb.DIRECTIVES = [];
|
||||||
QWeb.TEMPLATES = {};
|
QWeb.TEMPLATES = {};
|
||||||
@@ -2224,18 +2222,10 @@
|
|||||||
ctx.addToScope(name, `_${keysID}[${loopVar}]`);
|
ctx.addToScope(name, `_${keysID}[${loopVar}]`);
|
||||||
ctx.addToScope(name + "_value", `_${valuesID}[${loopVar}]`);
|
ctx.addToScope(name + "_value", `_${valuesID}[${loopVar}]`);
|
||||||
const nodeCopy = node.cloneNode(true);
|
const nodeCopy = node.cloneNode(true);
|
||||||
let shouldWarn = nodeCopy.tagName !== "t" && !nodeCopy.hasAttribute("t-key");
|
let shouldWarn = !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.length === 1 &&
|
||||||
node.children[0].tagName !== "t" &&
|
node.children[0].tagName !== "t" &&
|
||||||
!node.children[0].hasAttribute("t-key")) {
|
!node.children[0].hasAttribute("t-key");
|
||||||
shouldWarn = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldWarn) {
|
if (shouldWarn) {
|
||||||
console.warn(`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')`);
|
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}];`);
|
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
|
// t-component
|
||||||
@@ -2686,7 +2720,7 @@
|
|||||||
*/
|
*/
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "component",
|
name: "component",
|
||||||
extraNames: ["props", "keepalive"],
|
extraNames: ["props"],
|
||||||
priority: 100,
|
priority: 100,
|
||||||
atNodeEncounter({ ctx, value, node, qweb }) {
|
atNodeEncounter({ ctx, value, node, qweb }) {
|
||||||
ctx.addLine("//COMPONENT");
|
ctx.addLine("//COMPONENT");
|
||||||
@@ -2694,7 +2728,6 @@
|
|||||||
ctx.rootContext.shouldDefineQWeb = true;
|
ctx.rootContext.shouldDefineQWeb = true;
|
||||||
ctx.rootContext.shouldDefineParent = true;
|
ctx.rootContext.shouldDefineParent = true;
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
|
||||||
let hasDynamicProps = node.getAttribute("t-props") ? true : false;
|
let hasDynamicProps = node.getAttribute("t-props") ? true : false;
|
||||||
// t-on- events and t-transition
|
// t-on- events and t-transition
|
||||||
const events = [];
|
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
|
// computing the props string representing the props object
|
||||||
let propStr = Object.keys(props)
|
let propStr = Object.keys(props)
|
||||||
.map(k => k + ":" + props[k])
|
.map(k => k + ":" + props[k])
|
||||||
.join(",");
|
.join(",");
|
||||||
let defID = ctx.generateID();
|
let defID = ctx.generateID();
|
||||||
let componentID = 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()}__`;
|
let locationExpr = `\`__${ctx.generateID()}__`;
|
||||||
for (let i = 0; i < ctx.loopNumber - 1; i++) {
|
for (let i = 0; i < ctx.loopNumber - 1; i++) {
|
||||||
locationExpr += `\${i${i + 1}}__`;
|
locationExpr += `\${i${i + 1}}__`;
|
||||||
}
|
}
|
||||||
if (key || ctx.currentKey) {
|
if (ctx.lastNodeKey || ctx.currentKey) {
|
||||||
const k = key ? `key${keyID}` : ctx.currentKey;
|
const k = ctx.lastNodeKey || ctx.currentKey;
|
||||||
ctx.addLine(`let templateId${componentID} = ${locationExpr}\` + ${k};`);
|
ctx.addLine(`let templateId${componentID} = ${locationExpr}\` + ${k};`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -2765,8 +2788,8 @@
|
|||||||
if (transition) {
|
if (transition) {
|
||||||
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
|
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
|
||||||
}
|
}
|
||||||
let finalizeComponentCode = `w${componentID}.${keepAlive ? "unmount" : "destroy"}();`;
|
let finalizeComponentCode = `w${componentID}.destroy();`;
|
||||||
if (ref && !keepAlive) {
|
if (ref) {
|
||||||
finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`;
|
finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`;
|
||||||
}
|
}
|
||||||
if (transition) {
|
if (transition) {
|
||||||
@@ -2845,9 +2868,6 @@
|
|||||||
}
|
}
|
||||||
ctx.addLine(`let w${componentID} = ${templateId} in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[${templateId}]] : false;`);
|
ctx.addLine(`let w${componentID} = ${templateId} in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[${templateId}]] : false;`);
|
||||||
let shouldProxy = !ctx.parentNode;
|
let shouldProxy = !ctx.parentNode;
|
||||||
if (keepAlive) {
|
|
||||||
ctx.addLine(`const fiber${componentID} = Object.assign(Object.create(extra.fiber), {patchQueue: []});`);
|
|
||||||
}
|
|
||||||
if (shouldProxy) {
|
if (shouldProxy) {
|
||||||
let id = ctx.generateID();
|
let id = ctx.generateID();
|
||||||
ctx.rootContext.rootNode = id;
|
ctx.rootContext.rootNode = id;
|
||||||
@@ -2893,29 +2913,13 @@
|
|||||||
}
|
}
|
||||||
ctx.addIf(`w${componentID}`);
|
ctx.addIf(`w${componentID}`);
|
||||||
// need to update component
|
// 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 = "";
|
let styleCode = "";
|
||||||
if (tattStyle) {
|
if (tattStyle) {
|
||||||
styleCode = `.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};w${componentID}.el.style=${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};`);
|
", " + scopeVars}, sibling)${styleCode};`);
|
||||||
ctx.addLine(`let pvnode = w${componentID}.__owl__.pvnode;`);
|
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) {
|
if (registerCode) {
|
||||||
ctx.addLine(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
|
* Owl Fiber Class
|
||||||
*
|
*
|
||||||
@@ -2993,7 +3045,7 @@
|
|||||||
* states and in general determine the state of the rendering.
|
* states and in general determine the state of the rendering.
|
||||||
*/
|
*/
|
||||||
class Fiber {
|
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
|
// isCancelled means that the rendering corresponding to this fiber and its
|
||||||
// children is cancelled. No extra work should be done.
|
// children is cancelled. No extra work should be done.
|
||||||
this.isCancelled = false;
|
this.isCancelled = false;
|
||||||
@@ -3019,7 +3071,6 @@
|
|||||||
this.force = force;
|
this.force = force;
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.vars = vars;
|
this.vars = vars;
|
||||||
this.props = props;
|
|
||||||
this.component = component;
|
this.component = component;
|
||||||
this.root = parent ? parent.root : this;
|
this.root = parent ? parent.root : this;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@@ -3102,7 +3153,6 @@
|
|||||||
};
|
};
|
||||||
this._walk(doWork);
|
this._walk(doWork);
|
||||||
let component = this.component;
|
let component = this.component;
|
||||||
this.shouldPatch = false;
|
|
||||||
const patchLen = patchQueue.length;
|
const patchLen = patchQueue.length;
|
||||||
try {
|
try {
|
||||||
for (let i = 0; i < patchLen; i++) {
|
for (let i = 0; i < patchLen; i++) {
|
||||||
@@ -3116,16 +3166,11 @@
|
|||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
for (let i = 0; i < patchLen; i++) {
|
for (let i = 0; i < patchLen; i++) {
|
||||||
const fiber = patchQueue[i];
|
const fiber = patchQueue[i];
|
||||||
component = fiber.component;
|
component = fiber.component;
|
||||||
component.__patch(fiber.vnode);
|
component.__patch(fiber.vnode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
this.handleError(e);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
for (let i = patchLen - 1; i >= 0; i--) {
|
for (let i = patchLen - 1; i >= 0; i--) {
|
||||||
component = patchQueue[i].component;
|
component = patchQueue[i].component;
|
||||||
@@ -3138,7 +3183,6 @@
|
|||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
this.shouldPatch = true;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Cancel a fiber and all its children.
|
* Cancel a fiber and all its children.
|
||||||
@@ -3152,7 +3196,39 @@
|
|||||||
return f.child;
|
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
|
// optional prop
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!props[propName]) {
|
if (!(propName in props)) {
|
||||||
throw new Error(`Missing props '${propsDef[i]}' (component '${Widget.name}')`);
|
throw new Error(`Missing props '${propsDef[i]}' (component '${Widget.name}')`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3250,67 +3326,6 @@
|
|||||||
return result;
|
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
|
// Component
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -3322,47 +3337,32 @@
|
|||||||
/**
|
/**
|
||||||
* Creates an instance of Component.
|
* 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
|
* 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
|
* hand. Other components should be created automatically by the framework (with
|
||||||
* the t-component directive in a template)
|
* the t-component directive in a template)
|
||||||
*/
|
*/
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
const defaultProps = this.constructor.defaultProps;
|
|
||||||
Component.current = this;
|
Component.current = this;
|
||||||
|
const id = nextId++;
|
||||||
|
let depth;
|
||||||
|
if (parent) {
|
||||||
|
const defaultProps = this.constructor.defaultProps;
|
||||||
if (defaultProps) {
|
if (defaultProps) {
|
||||||
props = this.__applyDefaultProps(props, defaultProps);
|
props = this.__applyDefaultProps(props, defaultProps);
|
||||||
}
|
}
|
||||||
// is this a good idea?
|
this.props = props;
|
||||||
// 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;
|
|
||||||
if (QWeb.dev) {
|
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);
|
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, () => {
|
this.env.qweb.on("update", this, () => {
|
||||||
if (this.__owl__.isMounted) {
|
if (this.__owl__.isMounted) {
|
||||||
this.render(true);
|
this.render(true);
|
||||||
@@ -3376,15 +3376,17 @@
|
|||||||
this.env.qweb.off("update", this);
|
this.env.qweb.off("update", this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
depth = 0;
|
||||||
}
|
}
|
||||||
const qweb = this.env.qweb;
|
const qweb = this.env.qweb;
|
||||||
this.__owl__ = {
|
this.__owl__ = {
|
||||||
id: id,
|
id: id,
|
||||||
|
depth: depth,
|
||||||
vnode: null,
|
vnode: null,
|
||||||
pvnode: null,
|
pvnode: null,
|
||||||
isMounted: false,
|
isMounted: false,
|
||||||
isDestroyed: false,
|
isDestroyed: false,
|
||||||
parent: p,
|
parent: parent || null,
|
||||||
children: {},
|
children: {},
|
||||||
cmap: {},
|
cmap: {},
|
||||||
currentFiber: null,
|
currentFiber: null,
|
||||||
@@ -3474,11 +3476,6 @@
|
|||||||
* @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
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -3502,15 +3499,13 @@
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fiber = new Fiber(null, this, this.props, undefined, undefined, false);
|
return new Promise((resolve, reject) => {
|
||||||
if (!__owl__.vnode) {
|
const fiber = new Fiber(null, this, undefined, undefined, false);
|
||||||
this.__prepareAndRender(fiber);
|
scheduler.addFiber(fiber, err => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.__render(fiber);
|
|
||||||
}
|
|
||||||
return new Promise(resolve => {
|
|
||||||
scheduler.addFiber(fiber, () => {
|
|
||||||
if (!__owl__.isDestroyed) {
|
if (!__owl__.isDestroyed) {
|
||||||
this.__patch(fiber.vnode);
|
this.__patch(fiber.vnode);
|
||||||
target.appendChild(this.el);
|
target.appendChild(this.el);
|
||||||
@@ -3520,6 +3515,12 @@
|
|||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
if (!__owl__.vnode) {
|
||||||
|
this.__prepareAndRender(fiber);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.__render(fiber);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -3547,15 +3548,19 @@
|
|||||||
(__owl__.currentFiber && !__owl__.currentFiber.isRendered)) {
|
(__owl__.currentFiber && !__owl__.currentFiber.isRendered)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fiber = new Fiber(null, this, this.props, undefined, undefined, force);
|
return new Promise((resolve, reject) => {
|
||||||
this.__render(fiber);
|
const fiber = new Fiber(null, this, undefined, undefined, force);
|
||||||
return new Promise(resolve => {
|
scheduler.addFiber(fiber.root, err => {
|
||||||
scheduler.addFiber(fiber.root, () => {
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (__owl__.isMounted && fiber === fiber.root) {
|
if (__owl__.isMounted && fiber === fiber.root) {
|
||||||
fiber.patchComponents();
|
fiber.patchComponents();
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
this.__render(fiber);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -3680,7 +3685,7 @@
|
|||||||
const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps);
|
const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps);
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
const __owl__ = this.__owl__;
|
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) {
|
if (!parentFiber.child) {
|
||||||
parentFiber.child = fiber;
|
parentFiber.child = fiber;
|
||||||
}
|
}
|
||||||
@@ -3691,6 +3696,9 @@
|
|||||||
if (defaultProps) {
|
if (defaultProps) {
|
||||||
nextProps = this.__applyDefaultProps(nextProps, defaultProps);
|
nextProps = this.__applyDefaultProps(nextProps, defaultProps);
|
||||||
}
|
}
|
||||||
|
if (QWeb.dev) {
|
||||||
|
QWeb.utils.validateProps(this.constructor, nextProps);
|
||||||
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.willUpdateProps(nextProps),
|
this.willUpdateProps(nextProps),
|
||||||
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps)
|
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps)
|
||||||
@@ -3718,7 +3726,7 @@
|
|||||||
* parent template.
|
* parent template.
|
||||||
*/
|
*/
|
||||||
__prepare(parentFiber, scope, vars, previousSibling) {
|
__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;
|
fiber.shouldPatch = false;
|
||||||
if (!parentFiber.child) {
|
if (!parentFiber.child) {
|
||||||
parentFiber.child = fiber;
|
parentFiber.child = fiber;
|
||||||
@@ -3757,7 +3765,7 @@
|
|||||||
await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]);
|
await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
errorHandler(e, fiber);
|
fiber.handleError(e);
|
||||||
fiber.vnode = h("div"); // -> we render this div at the end
|
fiber.vnode = h("div"); // -> we render this div at the end
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -3782,7 +3790,7 @@
|
|||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
vnode = __owl__.vnode || h("div");
|
vnode = __owl__.vnode || h("div");
|
||||||
errorHandler(e, fiber);
|
fiber.handleError(e);
|
||||||
}
|
}
|
||||||
fiber.vnode = vnode;
|
fiber.vnode = vnode;
|
||||||
if (__owl__.observer) {
|
if (__owl__.observer) {
|
||||||
@@ -3846,40 +3854,6 @@
|
|||||||
Component._template = null;
|
Component._template = null;
|
||||||
Component.current = null;
|
Component.current = null;
|
||||||
Component.components = {};
|
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
|
* Owl Hook System
|
||||||
@@ -4011,6 +3985,27 @@
|
|||||||
* With a `Context` object, each component can subscribe (with the `useContext`
|
* 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.
|
* 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 {
|
class Context extends EventBus {
|
||||||
constructor(state = {}) {
|
constructor(state = {}) {
|
||||||
super();
|
super();
|
||||||
@@ -4020,37 +4015,44 @@
|
|||||||
this.observer = new Observer();
|
this.observer = new Observer();
|
||||||
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
this.observer.notifyCB = this.__notifyComponents.bind(this);
|
||||||
this.state = this.observer.observe(state);
|
this.state = this.observer.observe(state);
|
||||||
|
this.subscriptions.update = [];
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Instead of using trigger to emit an update event, we actually implement
|
* 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
|
* 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
|
* 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
|
* done before doing children components. More precisely, if an update
|
||||||
* as an effect of destroying a children, we do not want to call the
|
* as an effect of destroying a children, we do not want to call any code
|
||||||
* mapStoreToProps function of the child, nor rendering it.
|
* from the child, and certainly not render it.
|
||||||
*
|
*
|
||||||
* This method is not optimal if we have a bunch of asynchronous components:
|
* This method implements a simple grouping algorithm by depth. If we have
|
||||||
* we wait sequentially for each component to be completed before updating the
|
* connected components of depths [2, 4,4,4,4, 3,8,8], the Context will notify
|
||||||
* next. However, the only things that matters is that children are updated
|
* them in the following groups: [2], [4,4,4,4], [3], [8,8]. Each group will
|
||||||
* after their parents. So, this could be optimized by being smarter, and
|
* be updated sequentially, but each components in a given group will be done in
|
||||||
* updating all widgets concurrently, except for parents/children.
|
* parallel.
|
||||||
*
|
*
|
||||||
* A potential cheap way to improve this situation is to keep track of the
|
* This is a very simple algorithm, but it avoids checking if a given
|
||||||
* depth of a component in the component tree. A root component has a depth of
|
* component is a child of another.
|
||||||
* 1, then its children of 2 and so on... Then, we can update all components
|
|
||||||
* with the same depth in parallel.
|
|
||||||
*/
|
*/
|
||||||
async __notifyComponents() {
|
async __notifyComponents() {
|
||||||
const rev = ++this.rev;
|
const rev = ++this.rev;
|
||||||
const subs = this.subscriptions.update || [];
|
const subscriptions = this.subscriptions.update;
|
||||||
for (let i = 0, iLen = subs.length; i < iLen; i++) {
|
const groups = partitionBy(subscriptions, s => (s.owner ? s.owner.__owl__.depth : -1));
|
||||||
const sub = subs[i];
|
for (let group of groups) {
|
||||||
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
|
const proms = Promise.all(group.map(sub => {
|
||||||
if (shouldCallback) {
|
if (sub.owner ? sub.owner.__owl__.isMounted : true) {
|
||||||
const render = sub.callback.call(sub.owner, rev);
|
return sub.callback.call(sub.owner, rev);
|
||||||
scheduler.flush();
|
|
||||||
await render;
|
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
// 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 component = Component.current;
|
||||||
const store = options.store || component.env.store;
|
const store = options.store || component.env.store;
|
||||||
let result = selector(store.state, component.props);
|
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;
|
let revNumber = hashFn(result) || result;
|
||||||
const isEqual = options.isEqual || isStrictEqual;
|
const isEqual = options.isEqual || isStrictEqual;
|
||||||
if (!store.updateFunctions[component.__owl__.id]) {
|
if (!store.updateFunctions[component.__owl__.id]) {
|
||||||
@@ -4165,7 +4167,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
onWillUpdateProps(props => {
|
onWillUpdateProps(props => {
|
||||||
// FIXME: only do that if not keepalive + do it in destroy in that case
|
|
||||||
delete store.updateFunctions[component.__owl__.id];
|
delete store.updateFunctions[component.__owl__.id];
|
||||||
result = selector(store.state, props);
|
result = selector(store.state, props);
|
||||||
});
|
});
|
||||||
@@ -4373,7 +4374,7 @@
|
|||||||
// Private helpers
|
// Private helpers
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
setUrlFromPath(path) {
|
setUrlFromPath(path) {
|
||||||
const separator = this.mode === "hash" ? "/" : "";
|
const separator = this.mode === "hash" ? location.pathname : "";
|
||||||
const url = location.origin + separator + path;
|
const url = location.origin + separator + path;
|
||||||
if (url !== window.location.href) {
|
if (url !== window.location.href) {
|
||||||
window.history.pushState({}, path, url);
|
window.history.pushState({}, path, url);
|
||||||
@@ -4513,27 +4514,13 @@
|
|||||||
useStore: useStore
|
useStore: useStore
|
||||||
});
|
});
|
||||||
const __info__ = {};
|
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.Component = Component;
|
||||||
exports.Context = Context$1;
|
exports.Context = Context$1;
|
||||||
exports.QWeb = QWeb;
|
exports.QWeb = QWeb;
|
||||||
exports.Store = Store$1;
|
exports.Store = Store$1;
|
||||||
exports.__info__ = __info__;
|
exports.__info__ = __info__;
|
||||||
|
exports.config = config;
|
||||||
exports.core = core;
|
exports.core = core;
|
||||||
exports.hooks = hooks$1;
|
exports.hooks = hooks$1;
|
||||||
exports.misc = misc;
|
exports.misc = misc;
|
||||||
@@ -4542,9 +4529,9 @@
|
|||||||
exports.useState = useState$1;
|
exports.useState = useState$1;
|
||||||
exports.utils = utils;
|
exports.utils = utils;
|
||||||
|
|
||||||
exports.__info__.version = '0.24.0';
|
exports.__info__.version = '1.0.0-alpha';
|
||||||
exports.__info__.date = '2019-10-25T15:08:12.840Z';
|
exports.__info__.date = '2019-10-30T15:41:04.324Z';
|
||||||
exports.__info__.hash = 'f0b5a55';
|
exports.__info__.hash = '2fc71cf';
|
||||||
exports.__info__.url = 'https://github.com/odoo/owl';
|
exports.__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|
||||||
}(this.owl = this.owl || {}));
|
}(this.owl = this.owl || {}));
|
||||||
|
|||||||
+13
-3
@@ -90,7 +90,14 @@ function makeCodeIframe(js, css, xml, errorHandler) {
|
|||||||
owlScript.addEventListener("load", () => {
|
owlScript.addEventListener("load", () => {
|
||||||
const script = doc.createElement("script");
|
const script = doc.createElement("script");
|
||||||
script.type = "text/javascript";
|
script.type = "text/javascript";
|
||||||
const content = `owl.__info__.mode = 'dev';\nwindow.TEMPLATES = \`${sanitizedXML}\`\n${js}`;
|
const content = `
|
||||||
|
{
|
||||||
|
owl.__info__.mode = 'dev';
|
||||||
|
let templates = \`${sanitizedXML}\`;
|
||||||
|
const qweb = new owl.QWeb({ templates });
|
||||||
|
owl.config.env = { qweb };
|
||||||
|
}
|
||||||
|
${js}`;
|
||||||
script.innerHTML = content;
|
script.innerHTML = content;
|
||||||
iframe.contentWindow.addEventListener("error", errorHandler);
|
iframe.contentWindow.addEventListener("error", errorHandler);
|
||||||
iframe.contentWindow.addEventListener("unhandledrejection", errorHandler);
|
iframe.contentWindow.addEventListener("unhandledrejection", errorHandler);
|
||||||
@@ -429,12 +436,15 @@ App.components = { TabbedEditor };
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
async function start() {
|
async function start() {
|
||||||
document.title = `${document.title} (v${owl.__info__.version})`;
|
document.title = `${document.title} (v${owl.__info__.version})`;
|
||||||
|
const commit = `https://github.com/odoo/owl/commit/${owl.__info__.hash}`;
|
||||||
|
console.info(`This application is using Owl built with the following commit:`, commit);
|
||||||
const [templates] = await Promise.all([
|
const [templates] = await Promise.all([
|
||||||
owl.utils.loadFile("templates.xml"),
|
owl.utils.loadFile("templates.xml"),
|
||||||
owl.utils.whenReady()
|
owl.utils.whenReady()
|
||||||
]);
|
]);
|
||||||
const qweb = new owl.QWeb(templates);
|
const qweb = new owl.QWeb({ templates });
|
||||||
const app = new App({ qweb });
|
owl.config.env = { qweb };
|
||||||
|
const app = new App();
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-39
@@ -16,9 +16,7 @@ class App extends Component {
|
|||||||
App.components = { Greeter };
|
App.components = { Greeter };
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
// Note that the xml templates are injected into the global TEMPLATES variable.
|
const app = new App();
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
|
||||||
const app = new App({ qweb });
|
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -70,8 +68,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
App.components = { Counter };
|
App.components = { Counter };
|
||||||
|
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const app = new App();
|
||||||
const app = new App({qweb});
|
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -228,8 +225,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
App.components = { DemoComponent };
|
App.components = { DemoComponent };
|
||||||
|
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const app = new App();
|
||||||
const app = new App({ qweb });
|
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -298,8 +294,7 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const app = new App();
|
||||||
const app = new App({ qweb });
|
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -349,11 +344,9 @@ const themeContext = new Context({
|
|||||||
background: '#000',
|
background: '#000',
|
||||||
foreground: '#fff',
|
foreground: '#fff',
|
||||||
});
|
});
|
||||||
const env = {
|
// Add the themeContext the environment to make it available to all components
|
||||||
qweb: new owl.QWeb(TEMPLATES),
|
owl.config.env.themeContext = themeContext;
|
||||||
themeContext: themeContext,
|
const app = new App();
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -534,26 +527,25 @@ TodoApp.components = { TodoItem };
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// App Initialization
|
// App Initialization
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
function saveState(state) {
|
|
||||||
|
function makeStore() {
|
||||||
|
function saveState(state) {
|
||||||
const str = JSON.stringify(state);
|
const str = JSON.stringify(state);
|
||||||
window.localStorage.setItem(LOCALSTORAGE_KEY, str);
|
window.localStorage.setItem(LOCALSTORAGE_KEY, str);
|
||||||
}
|
}
|
||||||
|
function loadState() {
|
||||||
function loadState() {
|
|
||||||
const localState = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
const localState = window.localStorage.getItem(LOCALSTORAGE_KEY);
|
||||||
return localState ? JSON.parse(localState) : initialState;
|
return localState ? JSON.parse(localState) : initialState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeEnv() {
|
|
||||||
const state = loadState();
|
const state = loadState();
|
||||||
const store = new owl.Store({ state, actions });
|
const store = new owl.Store({ state, actions });
|
||||||
store.on("update", null, () => saveState(store.state));
|
store.on("update", null, () => saveState(store.state));
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
return store;
|
||||||
return { qweb, store };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const env = makeEnv();
|
owl.config.env.store = makeStore();
|
||||||
const app = new TodoApp(env);
|
const app = new TodoApp();
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1040,12 +1032,9 @@ function setupResponsivePlugin(env) {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Application Startup
|
// Application Startup
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
const env = {
|
setupResponsivePlugin(owl.config.env);
|
||||||
qweb: new owl.QWeb(TEMPLATES),
|
|
||||||
};
|
|
||||||
setupResponsivePlugin(env);
|
|
||||||
|
|
||||||
const app = new App(env);
|
const app = new App();
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1187,8 +1176,7 @@ class App extends Component {
|
|||||||
App.components = {Card, Counter};
|
App.components = {Card, Counter};
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const app = new App();
|
||||||
const app = new App({ qweb });
|
|
||||||
app.mount(document.body);`;
|
app.mount(document.body);`;
|
||||||
|
|
||||||
const SLOTS_XML = `<templates>
|
const SLOTS_XML = `<templates>
|
||||||
@@ -1301,10 +1289,9 @@ class App extends Component {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
App.components = {SlowComponent, NotificationList};
|
App.components = {SlowComponent, NotificationList, AsyncRoot};
|
||||||
|
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const app = new App();
|
||||||
const app = new App({ qweb });
|
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1373,8 +1360,7 @@ class Form extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const form = new Form();
|
||||||
const form = new Form({ qweb });
|
|
||||||
form.mount(document.body);
|
form.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1540,7 +1526,6 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
App.components = { WindowManager };
|
App.components = { WindowManager };
|
||||||
|
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
|
||||||
const windows = [
|
const windows = [
|
||||||
{
|
{
|
||||||
name: "Hello",
|
name: "Hello",
|
||||||
@@ -1558,8 +1543,8 @@ const windows = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const env = { qweb, windows };
|
owl.config.env.windows = windows;
|
||||||
const app = new App(env);
|
const app = new App();
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
t-att-style="topEditorStyle"/>
|
t-att-style="topEditorStyle"/>
|
||||||
<t t-if="state.splitLayout">
|
<t t-if="state.splitLayout">
|
||||||
<div class="separator horizontal"/>
|
<div class="separator horizontal"/>
|
||||||
<TabbedEditor t-keepalive="1"
|
<TabbedEditor
|
||||||
js="false"
|
js="false"
|
||||||
css="state.css"
|
css="state.css"
|
||||||
xml="state.xml"
|
xml="state.xml"
|
||||||
|
|||||||
Reference in New Issue
Block a user