mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] owl: update to v0.20.0
This commit is contained in:
@@ -410,8 +410,12 @@
|
|||||||
this.allowMultipleRoots = false;
|
this.allowMultipleRoots = false;
|
||||||
this.hasParentWidget = false;
|
this.hasParentWidget = false;
|
||||||
this.scopeVars = [];
|
this.scopeVars = [];
|
||||||
|
this.currentKey = "";
|
||||||
|
this.lastNodeKey = ""; // temp variable to communicate to previous caller
|
||||||
|
this.templates = {};
|
||||||
this.rootContext = this;
|
this.rootContext = this;
|
||||||
this.templateName = name || "noname";
|
this.templateName = name || "noname";
|
||||||
|
this.templates[this.templateName] = true;
|
||||||
this.addLine("var h = this.h;");
|
this.addLine("var h = this.h;");
|
||||||
}
|
}
|
||||||
generateID() {
|
generateID() {
|
||||||
@@ -1120,7 +1124,7 @@
|
|||||||
parentNode,
|
parentNode,
|
||||||
nextSibling,
|
nextSibling,
|
||||||
tagName,
|
tagName,
|
||||||
setTextContent,
|
setTextContent
|
||||||
};
|
};
|
||||||
function addNS(data, children, sel) {
|
function addNS(data, children, sel) {
|
||||||
data.ns = "http://www.w3.org/2000/svg";
|
data.ns = "http://www.w3.org/2000/svg";
|
||||||
@@ -1178,6 +1182,116 @@
|
|||||||
|
|
||||||
const patch = init([eventListenersModule, attrsModule, propsModule, classModule]);
|
const patch = init([eventListenersModule, attrsModule, propsModule, classModule]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Owl Utils
|
||||||
|
*
|
||||||
|
* We have here a small collection of utility functions:
|
||||||
|
*
|
||||||
|
* - whenReady
|
||||||
|
* - loadJS
|
||||||
|
* - loadTemplates
|
||||||
|
* - escape
|
||||||
|
* - debounce
|
||||||
|
*/
|
||||||
|
function whenReady(fn) {
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
if (document.readyState !== "loading") {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.addEventListener("DOMContentLoaded", resolve, false);
|
||||||
|
}
|
||||||
|
}).then(fn || function () { });
|
||||||
|
}
|
||||||
|
const loadedScripts = {};
|
||||||
|
function loadJS(url) {
|
||||||
|
if (url in loadedScripts) {
|
||||||
|
return loadedScripts[url];
|
||||||
|
}
|
||||||
|
const promise = new Promise(function (resolve, reject) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.src = url;
|
||||||
|
script.onload = function () {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = function () {
|
||||||
|
reject(`Error loading file '${url}'`);
|
||||||
|
};
|
||||||
|
const head = document.head || document.getElementsByTagName("head")[0];
|
||||||
|
head.appendChild(script);
|
||||||
|
});
|
||||||
|
loadedScripts[url] = promise;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
async function loadTemplates(url) {
|
||||||
|
const result = await fetch(url);
|
||||||
|
if (!result.ok) {
|
||||||
|
throw new Error("Error while fetching xml templates");
|
||||||
|
}
|
||||||
|
let templates = await result.text();
|
||||||
|
templates = templates.replace(/<!--[\s\S]*?-->/g, "");
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
function escape(str) {
|
||||||
|
if (str === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (typeof str === "number") {
|
||||||
|
return String(str);
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, "'")
|
||||||
|
.replace(/`/g, "`");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns a function, that, as long as it continues to be invoked, will not
|
||||||
|
* be triggered. The function will be called after it stops being called for
|
||||||
|
* N milliseconds. If `immediate` is passed, trigger the function on the
|
||||||
|
* leading edge, instead of the trailing.
|
||||||
|
*
|
||||||
|
* Inspired by https://davidwalsh.name/javascript-debounce-function
|
||||||
|
*/
|
||||||
|
function debounce(func, wait, immediate) {
|
||||||
|
let timeout;
|
||||||
|
return function () {
|
||||||
|
const context = this;
|
||||||
|
const args = arguments;
|
||||||
|
function later() {
|
||||||
|
timeout = null;
|
||||||
|
if (!immediate) {
|
||||||
|
func.apply(context, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const callNow = immediate && !timeout;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
if (callNow) {
|
||||||
|
func.apply(context, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function shallowEqual(p1, p2) {
|
||||||
|
for (let k in p1) {
|
||||||
|
if (p1[k] !== p2[k]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _utils = /*#__PURE__*/Object.freeze({
|
||||||
|
whenReady: whenReady,
|
||||||
|
loadJS: loadJS,
|
||||||
|
loadTemplates: loadTemplates,
|
||||||
|
escape: escape,
|
||||||
|
debounce: debounce,
|
||||||
|
shallowEqual: shallowEqual
|
||||||
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Const/global stuff/helpers
|
// Const/global stuff/helpers
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -1205,14 +1319,7 @@
|
|||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
},
|
},
|
||||||
shallowEqual(p1, p2) {
|
shallowEqual
|
||||||
for (let k in p1) {
|
|
||||||
if (p1[k] !== p2[k]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
function parseXML(xml) {
|
function parseXML(xml) {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
@@ -1259,6 +1366,10 @@
|
|||||||
// are meant to be used by the t-slot directive.
|
// are meant to be used by the t-slot directive.
|
||||||
this.slots = {};
|
this.slots = {};
|
||||||
this.nextSlotId = 1;
|
this.nextSlotId = 1;
|
||||||
|
// recursiveTemplates contains sub templates called with t-call, but which
|
||||||
|
// ends up in recursive situations. This is very similar to the slot situation,
|
||||||
|
// as in we need to propagate the scope.
|
||||||
|
this.recursiveFns = {};
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
if (data) {
|
if (data) {
|
||||||
this.addTemplates(data);
|
this.addTemplates(data);
|
||||||
@@ -1301,7 +1412,7 @@
|
|||||||
* template, with the name given by the t-name attribute.
|
* template, with the name given by the t-name attribute.
|
||||||
*/
|
*/
|
||||||
addTemplates(xmlstr) {
|
addTemplates(xmlstr) {
|
||||||
const doc = typeof xmlstr === 'string' ? parseXML(xmlstr) : xmlstr;
|
const doc = typeof xmlstr === "string" ? parseXML(xmlstr) : xmlstr;
|
||||||
const templates = doc.getElementsByTagName("templates")[0];
|
const templates = doc.getElementsByTagName("templates")[0];
|
||||||
if (!templates) {
|
if (!templates) {
|
||||||
return;
|
return;
|
||||||
@@ -1407,6 +1518,7 @@
|
|||||||
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 (parentContext) {
|
if (parentContext) {
|
||||||
|
ctx.templates = Object.create(parentContext.templates);
|
||||||
ctx.variables = Object.create(parentContext.variables);
|
ctx.variables = Object.create(parentContext.variables);
|
||||||
ctx.nextID = parentContext.parentNode + 1;
|
ctx.nextID = parentContext.parentNode + 1;
|
||||||
ctx.parentNode = parentContext.parentNode;
|
ctx.parentNode = parentContext.parentNode;
|
||||||
@@ -1543,6 +1655,7 @@
|
|||||||
if (node.nodeName !== "t") {
|
if (node.nodeName !== "t") {
|
||||||
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
||||||
ctx = ctx.withParent(nodeID);
|
ctx = ctx.withParent(nodeID);
|
||||||
|
ctx = ctx.subContext("currentKey", ctx.lastNodeKey);
|
||||||
let nodeHooks = {};
|
let nodeHooks = {};
|
||||||
let addNodeHook = function (hook, handler) {
|
let addNodeHook = function (hook, handler) {
|
||||||
nodeHooks[hook] = nodeHooks[hook] || [];
|
nodeHooks[hook] = nodeHooks[hook] || [];
|
||||||
@@ -1703,7 +1816,9 @@
|
|||||||
let nodeID = ctx.generateID();
|
let nodeID = ctx.generateID();
|
||||||
let nodeKey = node.getAttribute("t-key");
|
let nodeKey = node.getAttribute("t-key");
|
||||||
if (nodeKey) {
|
if (nodeKey) {
|
||||||
nodeKey = ctx.formatExpression(nodeKey);
|
ctx.addLine(`const nodeKey${nodeID} = ${ctx.formatExpression(nodeKey)}`);
|
||||||
|
nodeKey = `nodeKey${nodeID}`;
|
||||||
|
ctx.lastNodeKey = nodeKey;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
nodeKey = nodeID;
|
nodeKey = nodeID;
|
||||||
@@ -1838,6 +1953,7 @@
|
|||||||
if (node.nodeName !== "t") {
|
if (node.nodeName !== "t") {
|
||||||
let nodeID = qweb._compileGenericNode(node, ctx);
|
let nodeID = qweb._compileGenericNode(node, ctx);
|
||||||
ctx = ctx.withParent(nodeID);
|
ctx = ctx.withParent(nodeID);
|
||||||
|
ctx = ctx.subContext("currentKey", ctx.lastNodeKey);
|
||||||
}
|
}
|
||||||
let value = ctx.getValue(node.getAttribute("t-esc"));
|
let value = ctx.getValue(node.getAttribute("t-esc"));
|
||||||
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
|
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
|
||||||
@@ -1851,6 +1967,7 @@
|
|||||||
if (node.nodeName !== "t") {
|
if (node.nodeName !== "t") {
|
||||||
let nodeID = qweb._compileGenericNode(node, ctx);
|
let nodeID = qweb._compileGenericNode(node, ctx);
|
||||||
ctx = ctx.withParent(nodeID);
|
ctx = ctx.withParent(nodeID);
|
||||||
|
ctx = ctx.subContext("currentKey", ctx.lastNodeKey);
|
||||||
}
|
}
|
||||||
let value = ctx.getValue(node.getAttribute("t-raw"));
|
let value = ctx.getValue(node.getAttribute("t-raw"));
|
||||||
compileValueNode(value, node, qweb, ctx);
|
compileValueNode(value, node, qweb, ctx);
|
||||||
@@ -1952,8 +2069,46 @@
|
|||||||
qweb._compileNode(nodeCopy, tempCtx);
|
qweb._compileNode(nodeCopy, tempCtx);
|
||||||
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
|
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
|
||||||
ctx.rootContext.nextID = tempCtx.nextID;
|
ctx.rootContext.nextID = tempCtx.nextID;
|
||||||
|
const templateMap = Object.create(ctx.templates);
|
||||||
// open new scope, if necessary
|
// open new scope, if necessary
|
||||||
const hasNewVariables = Object.keys(tempCtx.variables).length > 0;
|
const hasNewVariables = Object.keys(tempCtx.variables).length > 0;
|
||||||
|
// compile sub template
|
||||||
|
let subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars));
|
||||||
|
subCtx = subCtx.subContext("templates", templateMap);
|
||||||
|
if (templateMap[subTemplate]) {
|
||||||
|
// OUCH, IT IS A RECURSIVE TEMPLATE SITUATION...
|
||||||
|
// This is a tricky situation... We obviously cannot inline the compiled
|
||||||
|
// template. So, what we need to do is to compile it, and make sure we
|
||||||
|
// properly transfer everything from the current scope to the sub template.
|
||||||
|
ctx.rootContext.shouldTrackScope = true;
|
||||||
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
|
let subTemplateName;
|
||||||
|
if (ctx.hasParentWidget) {
|
||||||
|
subTemplateName = ctx.templateName;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subTemplateName = `__${ctx.generateID()}`;
|
||||||
|
subCtx.variables = {};
|
||||||
|
let id = 0;
|
||||||
|
for (let v in vars) {
|
||||||
|
subCtx.variables[v] = vars[v];
|
||||||
|
vars[v].id = `_v${id++}`;
|
||||||
|
}
|
||||||
|
const subTemplateFn = qweb._compile(subTemplateName, nodeTemplate.elem, subCtx);
|
||||||
|
qweb.recursiveFns[subTemplateName] = subTemplateFn;
|
||||||
|
}
|
||||||
|
let varCode = `{}`;
|
||||||
|
if (Object.keys(vars).length) {
|
||||||
|
let id = 0;
|
||||||
|
const content = Object.values(vars)
|
||||||
|
.map((v) => `_v${id++}: ${v.expr}`)
|
||||||
|
.join(",");
|
||||||
|
varCode = `{${content}}`;
|
||||||
|
}
|
||||||
|
ctx.addLine(`this.recursiveFns['${subTemplateName}'].call(this, context, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: ${varCode}, scope}));`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
templateMap[subTemplate] = true;
|
||||||
if (hasNewVariables) {
|
if (hasNewVariables) {
|
||||||
ctx.addLine("{");
|
ctx.addLine("{");
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
@@ -1966,8 +2121,6 @@
|
|||||||
// todo: handle XML variables...
|
// todo: handle XML variables...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// compile sub template
|
|
||||||
const subCtx = ctx.subContext("caller", nodeCopy).subContext("variables", Object.create(vars));
|
|
||||||
qweb._compileNode(nodeTemplate.elem, subCtx);
|
qweb._compileNode(nodeTemplate.elem, subCtx);
|
||||||
// close new scope
|
// close new scope
|
||||||
if (hasNewVariables) {
|
if (hasNewVariables) {
|
||||||
@@ -2253,7 +2406,7 @@
|
|||||||
ctx.rootContext.shouldDefineOwner = true;
|
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}, vars: extra.vars, parent: owner}));`);
|
ctx.addLine(`slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: extra.vars, parent: owner}));`);
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2268,34 +2421,36 @@
|
|||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "model",
|
name: "model",
|
||||||
priority: 42,
|
priority: 42,
|
||||||
atNodeCreation({ ctx, nodeID, value, node, fullName }) {
|
atNodeCreation({ ctx, nodeID, value, node, fullName, addNodeHook }) {
|
||||||
const type = node.getAttribute("type");
|
const type = node.getAttribute("type");
|
||||||
let handler;
|
let handler;
|
||||||
let event = fullName.includes(".lazy") ? "change" : "input";
|
let event = fullName.includes(".lazy") ? "change" : "input";
|
||||||
|
const expr = ctx.formatExpression(`state.${value}`);
|
||||||
if (node.tagName === "select") {
|
if (node.tagName === "select") {
|
||||||
ctx.addLine(`p${nodeID}.props = {value: context.state['${value}']};`);
|
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
|
||||||
|
addNodeHook("create", `n.elm.value=${expr};`);
|
||||||
event = "change";
|
event = "change";
|
||||||
handler = `(ev) => {context.state['${value}'] = ev.target.value}`;
|
handler = `(ev) => {${expr} = ev.target.value}`;
|
||||||
}
|
}
|
||||||
else if (type === "checkbox") {
|
else if (type === "checkbox") {
|
||||||
ctx.addLine(`p${nodeID}.props = {checked: context.state['${value}']};`);
|
ctx.addLine(`p${nodeID}.props = {checked: ${expr}};`);
|
||||||
handler = `(ev) => {context.state['${value}'] = ev.target.checked}`;
|
handler = `(ev) => {${expr} = ev.target.checked}`;
|
||||||
}
|
}
|
||||||
else if (type === "radio") {
|
else if (type === "radio") {
|
||||||
const nodeValue = node.getAttribute("value");
|
const nodeValue = node.getAttribute("value");
|
||||||
ctx.addLine(`p${nodeID}.props = {checked:context.state['${value}'] === '${nodeValue}'};`);
|
ctx.addLine(`p${nodeID}.props = {checked:${expr} === '${nodeValue}'};`);
|
||||||
handler = `(ev) => {context.state['${value}'] = ev.target.value}`;
|
handler = `(ev) => {${expr} = ev.target.value}`;
|
||||||
event = "click";
|
event = "click";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ctx.addLine(`p${nodeID}.props = {value: context.state['${value}']};`);
|
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
|
||||||
const trimCode = fullName.includes(".trim") ? ".trim()" : "";
|
const trimCode = fullName.includes(".trim") ? ".trim()" : "";
|
||||||
let valueCode = `ev.target.value${trimCode}`;
|
let valueCode = `ev.target.value${trimCode}`;
|
||||||
if (fullName.includes(".number")) {
|
if (fullName.includes(".number")) {
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
valueCode = `utils.toNumber(${valueCode})`;
|
valueCode = `utils.toNumber(${valueCode})`;
|
||||||
}
|
}
|
||||||
handler = `(ev) => {context.state['${value}'] = ${valueCode}}`;
|
handler = `(ev) => {${expr} = ${valueCode}}`;
|
||||||
}
|
}
|
||||||
ctx.addLine(`extra.handlers['${event}' + ${nodeID}] = extra.handlers['${event}' + ${nodeID}] || (${handler});`);
|
ctx.addLine(`extra.handlers['${event}' + ${nodeID}] = extra.handlers['${event}' + ${nodeID}] || (${handler});`);
|
||||||
ctx.addLine(`p${nodeID}.on['${event}'] = extra.handlers['${event}' + ${nodeID}];`);
|
ctx.addLine(`p${nodeID}.on['${event}'] = extra.handlers['${event}' + ${nodeID}];`);
|
||||||
@@ -2534,24 +2689,23 @@
|
|||||||
if (key) {
|
if (key) {
|
||||||
// we bind a variable to the key (could be a complex expression, so we
|
// we bind a variable to the key (could be a complex expression, so we
|
||||||
// want to evaluate it only once)
|
// want to evaluate it only once)
|
||||||
ctx.addLine(`let key${keyID} = ${key};`);
|
ctx.addLine(`let key${keyID} = 'key' + ${key};`);
|
||||||
}
|
}
|
||||||
ctx.addLine(`let def${defID};`);
|
ctx.addLine(`let def${defID};`);
|
||||||
let templateID = key
|
let templateID = key
|
||||||
? `key${keyID}`
|
? `key${keyID}`
|
||||||
: ctx.inLoop
|
: ctx.inLoop
|
||||||
? `String(-${componentID} - i)`
|
? ctx.currentKey
|
||||||
|
? `String(${ctx.currentKey} + '_k_' + i + '_c_' + ${componentID} )`
|
||||||
|
: `String(-${componentID} - i)`
|
||||||
: String(componentID);
|
: String(componentID);
|
||||||
if (ctx.allowMultipleRoots) {
|
if (ctx.allowMultipleRoots) {
|
||||||
// necessary to prevent collisions
|
templateID = `"_slot_${templateID}"`;
|
||||||
if (!key && ctx.inLoop) {
|
}
|
||||||
let id = ctx.generateID();
|
if (key || ctx.inLoop) {
|
||||||
ctx.addLine(`let template${id} = "_slot_" + String(-${componentID} - i)`);
|
let id = ctx.generateID();
|
||||||
templateID = `template${id}`;
|
ctx.addLine(`let templateId${id} = ${templateID};`);
|
||||||
}
|
templateID = `templateId${id}`;
|
||||||
else {
|
|
||||||
templateID = `"_slot_${templateID}"`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let ref = node.getAttribute("t-ref");
|
let ref = node.getAttribute("t-ref");
|
||||||
let refExpr = "";
|
let refExpr = "";
|
||||||
@@ -2708,7 +2862,7 @@
|
|||||||
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);
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||||
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
qweb.slots[`${slotId}_${key}`] = slotFn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clone.childNodes.length) {
|
if (clone.childNodes.length) {
|
||||||
@@ -2717,7 +2871,7 @@
|
|||||||
t.appendChild(child);
|
t.appendChild(child);
|
||||||
}
|
}
|
||||||
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
||||||
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
qweb.slots[`${slotId}_default`] = slotFn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let scopeVars = "";
|
let scopeVars = "";
|
||||||
@@ -2733,7 +2887,7 @@
|
|||||||
if (shouldProxy) {
|
if (shouldProxy) {
|
||||||
registerCode = `utils.defineProxy(vn${ctx.rootNode}, pvnode);`;
|
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.addLine(`def${defID} = def${defID}.then(vnode=>{if (w${componentID}.__owl__.isDestroyed){return}${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
|
||||||
let patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
let patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
||||||
@@ -3506,6 +3660,7 @@
|
|||||||
const observer = store.observer;
|
const observer = store.observer;
|
||||||
const revFn = this.deep ? observer.deepRevNumber : observer.revNumber;
|
const revFn = this.deep ? observer.deepRevNumber : observer.revNumber;
|
||||||
this.__owl__.store = store;
|
this.__owl__.store = store;
|
||||||
|
this.__owl__.ownProps = this.props;
|
||||||
this.__owl__.revFn = revFn.bind(observer);
|
this.__owl__.revFn = revFn.bind(observer);
|
||||||
this.__owl__.storeHash = this.hashFunction(this.storeProps, {
|
this.__owl__.storeHash = this.hashFunction(this.storeProps, {
|
||||||
prevStoreProps: this.storeProps
|
prevStoreProps: this.storeProps
|
||||||
@@ -3530,34 +3685,60 @@
|
|||||||
this.__owl__.store.off("update", this);
|
this.__owl__.store.off("update", this);
|
||||||
super.__destroy(parent);
|
super.__destroy(parent);
|
||||||
}
|
}
|
||||||
|
async render(force = false) {
|
||||||
|
this.__updateStoreProps(this.props);
|
||||||
|
// this is quite technical, so this deserves some explanation.
|
||||||
|
// When we have a connected component, it can be updated for 3 reasons:
|
||||||
|
// - some internal state changes (this will go through this method)
|
||||||
|
// - some props changes (if a parent is changed and need to rerender itself)
|
||||||
|
// - a store update
|
||||||
|
//
|
||||||
|
// It is possible (with connected component and parent) to have the following
|
||||||
|
// situation: the parent component is rendered first (from its state change),
|
||||||
|
// then immediately after, it is rendered (from store update). Then, if the
|
||||||
|
// __checkUpdate method is immediately over, the children component will
|
||||||
|
// be rendered again by the store update, even though it is supposed to be
|
||||||
|
// destroyed by the first rendering.
|
||||||
|
//
|
||||||
|
// So, the solution is to keep the information that there is a current
|
||||||
|
// rendering occuring with the same store state, the same props, and return
|
||||||
|
// that in the __checkUpdate method. To do this, we use the renderPromise
|
||||||
|
// deferred, which is not used by the component system once the
|
||||||
|
// component is ready, so we can use it for our own purpose.
|
||||||
|
this.__owl__.renderPromise = super.render(force);
|
||||||
|
return this.__owl__.renderPromise;
|
||||||
|
}
|
||||||
async __updateProps(nextProps, f, p, s, v) {
|
async __updateProps(nextProps, f, p, s, v) {
|
||||||
this.__updateStoreProps(nextProps);
|
this.__updateStoreProps(nextProps);
|
||||||
return super.__updateProps(nextProps, f, p, s, v);
|
return super.__updateProps(nextProps, f, p, s, v);
|
||||||
}
|
}
|
||||||
__updateStoreProps(nextProps) {
|
__updateStoreProps(nextProps) {
|
||||||
const store = this.__owl__.store;
|
const __owl__ = this.__owl__;
|
||||||
|
const store = __owl__.store;
|
||||||
|
const observer = store.observer;
|
||||||
|
if (observer.rev === __owl__.rev && nextProps === __owl__.ownProps) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const storeProps = this.constructor.mapStoreToProps(store.state, nextProps, store.getters);
|
const storeProps = this.constructor.mapStoreToProps(store.state, nextProps, store.getters);
|
||||||
const options = { prevStoreProps: this.storeProps, didChange: false };
|
const options = { prevStoreProps: this.storeProps, didChange: false };
|
||||||
const storeHash = this.hashFunction(storeProps, options);
|
const storeHash = this.hashFunction(storeProps, options);
|
||||||
this.storeProps = storeProps;
|
this.storeProps = storeProps;
|
||||||
let didChange = options.didChange;
|
let didChange = options.didChange;
|
||||||
if (storeHash !== this.__owl__.storeHash) {
|
if (storeHash !== __owl__.storeHash) {
|
||||||
this.__owl__.storeHash = storeHash;
|
__owl__.storeHash = storeHash;
|
||||||
didChange = true;
|
didChange = true;
|
||||||
}
|
}
|
||||||
this.__owl__.rev = store.observer.rev;
|
__owl__.rev = store.observer.rev;
|
||||||
|
__owl__.ownProps = nextProps;
|
||||||
return didChange;
|
return didChange;
|
||||||
}
|
}
|
||||||
async __checkUpdate() {
|
async __checkUpdate() {
|
||||||
const observer = this.__owl__.store.observer;
|
|
||||||
if (observer.rev === this.__owl__.rev) {
|
|
||||||
// update was already done by updateProps, from parent
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const didChange = this.__updateStoreProps(this.props);
|
const didChange = this.__updateStoreProps(this.props);
|
||||||
if (didChange) {
|
if (didChange) {
|
||||||
return this.render();
|
return this.render();
|
||||||
}
|
}
|
||||||
|
// see note in render method
|
||||||
|
return this.__owl__.renderPromise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3574,7 +3755,7 @@
|
|||||||
if (config.getters) {
|
if (config.getters) {
|
||||||
const firstArg = {
|
const firstArg = {
|
||||||
state: this.state,
|
state: this.state,
|
||||||
getters: this.getters,
|
getters: this.getters
|
||||||
};
|
};
|
||||||
for (let g in config.getters) {
|
for (let g in config.getters) {
|
||||||
this.getters[g] = config.getters[g].bind(this, firstArg);
|
this.getters[g] = config.getters[g].bind(this, firstArg);
|
||||||
@@ -3619,107 +3800,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Owl Utils
|
|
||||||
*
|
|
||||||
* We have here a small collection of utility functions:
|
|
||||||
*
|
|
||||||
* - whenReady
|
|
||||||
* - loadJS
|
|
||||||
* - loadTemplates
|
|
||||||
* - escape
|
|
||||||
* - debounce
|
|
||||||
*/
|
|
||||||
function whenReady(fn) {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
if (document.readyState !== "loading") {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.addEventListener("DOMContentLoaded", resolve, false);
|
|
||||||
}
|
|
||||||
}).then(fn || function () { });
|
|
||||||
}
|
|
||||||
const loadedScripts = {};
|
|
||||||
function loadJS(url) {
|
|
||||||
if (url in loadedScripts) {
|
|
||||||
return loadedScripts[url];
|
|
||||||
}
|
|
||||||
const promise = new Promise(function (resolve, reject) {
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.type = "text/javascript";
|
|
||||||
script.src = url;
|
|
||||||
script.onload = function () {
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
script.onerror = function () {
|
|
||||||
reject(`Error loading file '${url}'`);
|
|
||||||
};
|
|
||||||
const head = document.head || document.getElementsByTagName("head")[0];
|
|
||||||
head.appendChild(script);
|
|
||||||
});
|
|
||||||
loadedScripts[url] = promise;
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
async function loadTemplates(url) {
|
|
||||||
const result = await fetch(url);
|
|
||||||
if (!result.ok) {
|
|
||||||
throw new Error("Error while fetching xml templates");
|
|
||||||
}
|
|
||||||
let templates = await result.text();
|
|
||||||
templates = templates.replace(/<!--[\s\S]*?-->/g, "");
|
|
||||||
return templates;
|
|
||||||
}
|
|
||||||
function escape(str) {
|
|
||||||
if (str === undefined) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (typeof str === "number") {
|
|
||||||
return String(str);
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, "'")
|
|
||||||
.replace(/`/g, "`");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns a function, that, as long as it continues to be invoked, will not
|
|
||||||
* be triggered. The function will be called after it stops being called for
|
|
||||||
* N milliseconds. If `immediate` is passed, trigger the function on the
|
|
||||||
* leading edge, instead of the trailing.
|
|
||||||
*
|
|
||||||
* Inspired by https://davidwalsh.name/javascript-debounce-function
|
|
||||||
*/
|
|
||||||
function debounce(func, wait, immediate) {
|
|
||||||
let timeout;
|
|
||||||
return function () {
|
|
||||||
const context = this;
|
|
||||||
const args = arguments;
|
|
||||||
function later() {
|
|
||||||
timeout = null;
|
|
||||||
if (!immediate) {
|
|
||||||
func.apply(context, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const callNow = immediate && !timeout;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(later, wait);
|
|
||||||
if (callNow) {
|
|
||||||
func.apply(context, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var _utils = /*#__PURE__*/Object.freeze({
|
|
||||||
whenReady: whenReady,
|
|
||||||
loadJS: loadJS,
|
|
||||||
loadTemplates: loadTemplates,
|
|
||||||
escape: escape,
|
|
||||||
debounce: debounce
|
|
||||||
});
|
|
||||||
|
|
||||||
const LINK_TEMPLATE_NAME = "__owl__-router-link";
|
const LINK_TEMPLATE_NAME = "__owl__-router-link";
|
||||||
const LINK_TEMPLATE = `
|
const LINK_TEMPLATE = `
|
||||||
<a t-att-class="{'router-link-active': isActive }"
|
<a t-att-class="{'router-link-active': isActive }"
|
||||||
@@ -3815,8 +3895,6 @@
|
|||||||
this.routes[partialRoute.name] = partialRoute;
|
this.routes[partialRoute.name] = partialRoute;
|
||||||
this.routeIds.push(partialRoute.name);
|
this.routeIds.push(partialRoute.name);
|
||||||
}
|
}
|
||||||
this._listener = () => this.matchAndApplyRules(this.currentPath());
|
|
||||||
window.addEventListener("popstate", this._listener);
|
|
||||||
// setup link and directive
|
// setup link and directive
|
||||||
env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE);
|
env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE);
|
||||||
QWeb.addDirective(makeDirective(env));
|
QWeb.addDirective(makeDirective(env));
|
||||||
@@ -3825,6 +3903,11 @@
|
|||||||
// Public API
|
// Public API
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
async start() {
|
async start() {
|
||||||
|
this._listener = ev => this._navigate(this.currentPath(), ev);
|
||||||
|
window.addEventListener("popstate", this._listener);
|
||||||
|
if (this.mode === "hash") {
|
||||||
|
window.addEventListener("hashchange", this._listener);
|
||||||
|
}
|
||||||
const result = await this.matchAndApplyRules(this.currentPath());
|
const result = await this.matchAndApplyRules(this.currentPath());
|
||||||
if (result.type === "match") {
|
if (result.type === "match") {
|
||||||
this.currentRoute = result.route;
|
this.currentRoute = result.route;
|
||||||
@@ -3837,11 +3920,18 @@
|
|||||||
}
|
}
|
||||||
async navigate(to) {
|
async navigate(to) {
|
||||||
const path = this.destToPath(to);
|
const path = this.destToPath(to);
|
||||||
|
return this._navigate(path);
|
||||||
|
}
|
||||||
|
async _navigate(path, ev) {
|
||||||
const initialName = this.currentRouteName;
|
const initialName = this.currentRouteName;
|
||||||
|
const initialParams = this.currentParams;
|
||||||
const result = await this.matchAndApplyRules(path);
|
const result = await this.matchAndApplyRules(path);
|
||||||
if (result.type === "match") {
|
if (result.type === "match") {
|
||||||
const finalPath = this.routeToPath(result.route, result.params);
|
const finalPath = this.routeToPath(result.route, result.params);
|
||||||
this.setUrlFromPath(finalPath);
|
const isPopStateEvent = ev && ev instanceof PopStateEvent;
|
||||||
|
if (!isPopStateEvent) {
|
||||||
|
this.setUrlFromPath(finalPath);
|
||||||
|
}
|
||||||
this.currentRoute = result.route;
|
this.currentRoute = result.route;
|
||||||
this.currentParams = result.params;
|
this.currentParams = result.params;
|
||||||
}
|
}
|
||||||
@@ -3849,7 +3939,8 @@
|
|||||||
this.currentRoute = null;
|
this.currentRoute = null;
|
||||||
this.currentParams = null;
|
this.currentParams = null;
|
||||||
}
|
}
|
||||||
if (this.currentRouteName !== initialName) {
|
const didChange = this.currentRouteName !== initialName || !shallowEqual(this.currentParams, initialParams);
|
||||||
|
if (didChange) {
|
||||||
this.env.qweb.forceUpdate();
|
this.env.qweb.forceUpdate();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3866,8 +3957,11 @@
|
|||||||
// Private helpers
|
// Private helpers
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
setUrlFromPath(path) {
|
setUrlFromPath(path) {
|
||||||
const url = location.origin + path;
|
const separator = this.mode === "hash" ? "/" : "";
|
||||||
window.history.pushState({}, path, url);
|
const url = location.origin + separator + path;
|
||||||
|
if (url !== window.location.href) {
|
||||||
|
window.history.pushState({}, path, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
validateDestination(dest) {
|
validateDestination(dest) {
|
||||||
if ((!dest.path && !dest.to) || (dest.path && dest.to)) {
|
if ((!dest.path && !dest.to) || (dest.path && dest.to)) {
|
||||||
@@ -3886,7 +3980,8 @@
|
|||||||
parts[i] = params[key];
|
parts[i] = params[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return parts.join("/");
|
const prefix = this.mode === "hash" ? "#" : "";
|
||||||
|
return prefix + parts.join("/");
|
||||||
}
|
}
|
||||||
currentPath() {
|
currentPath() {
|
||||||
let result = this.mode === "history" ? window.location.pathname : window.location.hash.slice(1);
|
let result = this.mode === "history" ? window.location.pathname : window.location.hash.slice(1);
|
||||||
@@ -3940,6 +4035,9 @@
|
|||||||
if (route.path === "*") {
|
if (route.path === "*") {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
if (path.startsWith("#")) {
|
||||||
|
path = path.slice(1);
|
||||||
|
}
|
||||||
const descrParts = route.path.split("/");
|
const descrParts = route.path.split("/");
|
||||||
const targetParts = path.split("/");
|
const targetParts = path.split("/");
|
||||||
const l = descrParts.length;
|
const l = descrParts.length;
|
||||||
@@ -4013,9 +4111,9 @@
|
|||||||
exports.store = store;
|
exports.store = store;
|
||||||
exports.utils = utils;
|
exports.utils = utils;
|
||||||
|
|
||||||
exports.__info__.version = '0.19.0';
|
exports.__info__.version = '0.20.0';
|
||||||
exports.__info__.date = '2019-08-28T09:55:59.136Z';
|
exports.__info__.date = '2019-09-05T12:42:15.744Z';
|
||||||
exports.__info__.hash = 'f289cb1';
|
exports.__info__.hash = '9fad164';
|
||||||
exports.__info__.url = 'https://github.com/odoo/owl';
|
exports.__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|
||||||
}(this.owl = this.owl || {}));
|
}(this.owl = this.owl || {}));
|
||||||
|
|||||||
Reference in New Issue
Block a user