2019-07-14 22:18:00 +02:00
|
|
|
import { QWeb } from "../qweb/index";
|
|
|
|
|
import { MODS_CODE } from "../qweb/extensions";
|
2019-05-06 12:34:36 +02:00
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
2019-06-20 09:42:33 +02:00
|
|
|
// t-component
|
2019-05-06 12:34:36 +02:00
|
|
|
//------------------------------------------------------------------------------
|
2019-05-17 10:49:04 +02:00
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
const T_COMPONENT_MODS_CODE = Object.assign({}, MODS_CODE, {
|
2019-06-03 15:12:21 +02:00
|
|
|
self: "if (e.target !== vn.elm) {return}"
|
2019-06-03 14:28:10 +02:00
|
|
|
});
|
|
|
|
|
|
2019-07-14 22:18:00 +02:00
|
|
|
QWeb.utils.defineProxy = function defineProxy(target, source) {
|
2019-07-11 16:19:59 +02:00
|
|
|
for (let k in source) {
|
|
|
|
|
Object.defineProperty(target, k, {
|
|
|
|
|
get() {
|
|
|
|
|
return source[k];
|
|
|
|
|
},
|
|
|
|
|
set(val) {
|
|
|
|
|
source[k] = val;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-17 10:49:04 +02:00
|
|
|
/**
|
2019-06-20 09:42:33 +02:00
|
|
|
* The t-component directive is certainly a complicated and hard to maintain piece
|
2019-05-17 10:49:04 +02:00
|
|
|
* of code. To help you, fellow developer, if you have to maintain it, I offer
|
|
|
|
|
* you this advice: Good luck...
|
|
|
|
|
*
|
|
|
|
|
* Since it is not 'direct' code, but rather code that generates other code, it
|
|
|
|
|
* is not easy to understand. To help you, here is a detailed and commented
|
2019-06-20 09:42:33 +02:00
|
|
|
* explanation of the code generated by the t-component directive for the following
|
2019-05-17 10:49:04 +02:00
|
|
|
* situation:
|
|
|
|
|
* ```xml
|
2019-06-18 09:24:52 +02:00
|
|
|
* <Child
|
2019-05-20 10:45:26 +02:00
|
|
|
* t-key="'somestring'"
|
2019-06-03 11:15:13 +02:00
|
|
|
* flag="state.flag"
|
2019-05-20 10:45:26 +02:00
|
|
|
* t-transition="fade"/>
|
2019-05-17 10:49:04 +02:00
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* ```js
|
2019-06-01 21:44:43 +02:00
|
|
|
* // we assign utils on top of the function because it will be useful for
|
2019-06-20 09:42:33 +02:00
|
|
|
* // each components
|
2019-06-01 21:44:43 +02:00
|
|
|
* let utils = this.utils;
|
|
|
|
|
*
|
2019-05-17 10:49:04 +02:00
|
|
|
* // this is the virtual node representing the parent div
|
|
|
|
|
* let c1 = [], p1 = { key: 1 };
|
|
|
|
|
* var vn1 = h("div", p1, c1);
|
|
|
|
|
*
|
2019-06-20 09:42:33 +02:00
|
|
|
* // t-component directive: we start by evaluating the expression given by t-key:
|
2019-05-17 10:49:04 +02:00
|
|
|
* let key5 = "somestring";
|
|
|
|
|
*
|
2019-06-20 09:42:33 +02:00
|
|
|
* // def3 is the promise that will contain later either the new component
|
2019-05-17 10:49:04 +02:00
|
|
|
* // creation, or the props update...
|
|
|
|
|
* let def3;
|
|
|
|
|
*
|
2019-06-20 09:42:33 +02:00
|
|
|
* // this is kind of tricky: we need here to find if the component was already
|
2019-05-17 10:49:04 +02:00
|
|
|
* // created by a previous rendering. This is done by checking the internal
|
2019-06-20 09:42:33 +02:00
|
|
|
* // `cmap` (children map) of the parent component: it maps keys to component ids,
|
2019-05-17 10:49:04 +02:00
|
|
|
* // and, then, if there is an id, we look into the children list to get the
|
|
|
|
|
* // instance
|
|
|
|
|
* let w4 =
|
|
|
|
|
* key5 in context.__owl__.cmap
|
|
|
|
|
* ? context.__owl__.children[context.__owl__.cmap[key5]]
|
|
|
|
|
* : false;
|
|
|
|
|
*
|
2019-06-20 09:42:33 +02:00
|
|
|
* // We keep the index of the position of the component in the closure. We push
|
|
|
|
|
* // null to reserve the slot, and will replace it later by the component vnode,
|
|
|
|
|
* // when it will be ready (do not forget that preparing/rendering a component is
|
2019-06-14 10:17:13 +02:00
|
|
|
* // asynchronous)
|
|
|
|
|
* let _2_index = c1.length;
|
|
|
|
|
* c1.push(null);
|
|
|
|
|
*
|
2019-05-17 10:49:04 +02:00
|
|
|
* // we evaluate here the props given to the component. It is done here to be
|
|
|
|
|
* // able to easily reference it later, and also, it might be an expensive
|
|
|
|
|
* // computation, so it is certainly better to do it only once
|
|
|
|
|
* let props4 = { flag: context["state"].flag };
|
|
|
|
|
*
|
2019-06-20 09:42:33 +02:00
|
|
|
* // If we have a component, currently rendering, but not ready yet, we do not want
|
2019-06-01 21:44:43 +02:00
|
|
|
* // to wait for it to be ready if we can avoid it
|
|
|
|
|
* if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
|
|
|
|
* // we check if the props are the same. In that case, we can simply reuse
|
|
|
|
|
* // the previous rendering and skip all useless work
|
|
|
|
|
* if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
|
|
|
|
* def3 = w4.__owl__.renderPromise;
|
|
|
|
|
* } else {
|
2019-06-20 09:42:33 +02:00
|
|
|
* // if the props are not the same, we destroy the component and starts anew.
|
2019-06-01 21:44:43 +02:00
|
|
|
* // this will be faster than waiting for its rendering, then updating it
|
|
|
|
|
* w4.destroy();
|
|
|
|
|
* w4 = false;
|
|
|
|
|
* }
|
2019-05-17 10:49:04 +02:00
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* if (!w4) {
|
2019-06-20 09:42:33 +02:00
|
|
|
* // in this situation, we need to create a new component. First step is
|
2019-05-17 10:49:04 +02:00
|
|
|
* // to get a reference to the class, then create an instance with
|
|
|
|
|
* // current context as parent, and the props.
|
2019-06-20 09:42:33 +02:00
|
|
|
* let W4 = context.component && context.components[componentKey4] || QWeb.component[componentKey4];
|
2019-05-31 12:39:40 +02:00
|
|
|
|
2019-05-17 10:49:04 +02:00
|
|
|
* if (!W4) {
|
2019-06-20 09:42:33 +02:00
|
|
|
* throw new Error("Cannot find the definition of component 'child'");
|
2019-05-17 10:49:04 +02:00
|
|
|
* }
|
|
|
|
|
* w4 = new W4(owner, props4);
|
|
|
|
|
*
|
2019-06-20 09:42:33 +02:00
|
|
|
* // Whenever we rerender the parent component, we need to be sure that we
|
|
|
|
|
* // are able to find the component instance. To do that, we register it to
|
2019-05-17 10:49:04 +02:00
|
|
|
* // the parent cmap (children map). Note that the 'template' key is
|
2019-06-20 09:42:33 +02:00
|
|
|
* // used here, since this is what identify the component from the template
|
2019-05-17 10:49:04 +02:00
|
|
|
* // perspective.
|
|
|
|
|
* context.__owl__.cmap[key5] = w4.__owl__.id;
|
|
|
|
|
*
|
2019-06-24 13:02:12 +02:00
|
|
|
* // __prepare is called, to basically call willStart, then render the
|
2019-06-20 09:42:33 +02:00
|
|
|
* // component
|
2019-06-24 13:02:12 +02:00
|
|
|
* def3 = w4.__prepare();
|
2019-05-17 10:49:04 +02:00
|
|
|
*
|
|
|
|
|
* def3 = def3.then(vnode => {
|
2019-06-20 09:42:33 +02:00
|
|
|
* // we create here a virtual node for the parent (NOT the component). This
|
2019-05-17 10:49:04 +02:00
|
|
|
* // means that the vdom of the parent will be stopped here, and from
|
|
|
|
|
* // the parent's perspective, it simply is a vnode with no children.
|
|
|
|
|
* // However, it shares the same dom element with the component root
|
|
|
|
|
* // vnode.
|
|
|
|
|
* let pvnode = h(vnode.sel, { key: key5 });
|
|
|
|
|
*
|
|
|
|
|
* // we add hooks to the parent vnode so we can interact with the new
|
2019-06-20 09:42:33 +02:00
|
|
|
* // component at the proper time
|
2019-05-17 10:49:04 +02:00
|
|
|
* pvnode.data.hook = {
|
|
|
|
|
* insert(vn) {
|
2019-06-24 13:02:12 +02:00
|
|
|
* // the __mount method will patch the component vdom into the elm vn.elm,
|
2019-05-17 10:49:04 +02:00
|
|
|
* // then call the mounted hooks. However, suprisingly, the snabbdom
|
|
|
|
|
* // patch method actually replace the elm by a new elm, so we need
|
|
|
|
|
* // to synchronise the pvnode elm with the resulting elm
|
2019-06-24 13:02:12 +02:00
|
|
|
* let nvn = w4.__mount(vnode, vn.elm);
|
2019-05-17 10:49:04 +02:00
|
|
|
* pvnode.elm = nvn.elm;
|
2019-06-20 09:42:33 +02:00
|
|
|
* // what follows is only present if there are animations on the component
|
2019-06-11 14:48:37 +02:00
|
|
|
* utils.transitionInsert(vn, "fade");
|
2019-05-17 10:49:04 +02:00
|
|
|
* },
|
|
|
|
|
* remove() {
|
2019-05-20 10:45:26 +02:00
|
|
|
* // override with empty function to prevent from removing the node
|
|
|
|
|
* // directly. It will be removed when destroy is called anyway, which
|
|
|
|
|
* // delays the removal if there are animations.
|
2019-05-17 10:49:04 +02:00
|
|
|
* },
|
|
|
|
|
* destroy() {
|
2019-05-20 10:45:26 +02:00
|
|
|
* // if there are animations, we delay the call to destroy on the
|
2019-06-20 09:42:33 +02:00
|
|
|
* // component, if not, we call it directly.
|
2019-05-20 10:45:26 +02:00
|
|
|
* let finalize = () => {
|
|
|
|
|
* w4.destroy();
|
|
|
|
|
* };
|
2019-06-11 14:48:37 +02:00
|
|
|
* utils.transitionRemove(vn, "fade", finalize);
|
2019-05-17 10:49:04 +02:00
|
|
|
* }
|
|
|
|
|
* };
|
|
|
|
|
* // the pvnode is inserted at the correct position in the div's children
|
|
|
|
|
* c1[_2_index] = pvnode;
|
|
|
|
|
*
|
|
|
|
|
* // we keep here a reference to the parent vnode (representing the
|
2019-06-20 09:42:33 +02:00
|
|
|
* // component, so we can reuse it later whenever we update the component
|
2019-05-17 10:49:04 +02:00
|
|
|
* w4.__owl__.pvnode = pvnode;
|
|
|
|
|
* });
|
|
|
|
|
* } else {
|
|
|
|
|
* // this is the 'update' path of the directive.
|
2019-06-24 13:02:12 +02:00
|
|
|
* // the call to __updateProps is the actual component update
|
2019-06-01 21:44:43 +02:00
|
|
|
* // Note that we only update the props if we cannot reuse the previous
|
|
|
|
|
* // rendering work (in the case it was rendered with the same props)
|
2019-06-24 13:02:12 +02:00
|
|
|
* def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
2019-05-17 10:49:04 +02:00
|
|
|
* def3 = def3.then(() => {
|
2019-06-20 09:42:33 +02:00
|
|
|
* // if component was destroyed in the meantime, we do nothing (so, this
|
2019-05-17 10:49:04 +02:00
|
|
|
* // means that the parent's element children list will have a null in
|
2019-06-20 09:42:33 +02:00
|
|
|
* // the component's position, which will cause the pvnode to be removed
|
2019-05-17 10:49:04 +02:00
|
|
|
* // when it is patched.
|
|
|
|
|
* if (w4.__owl__.isDestroyed) {
|
|
|
|
|
* return;
|
|
|
|
|
* }
|
|
|
|
|
* // like above, we register the pvnode to the children list, so it
|
|
|
|
|
* // will not be patched out of the dom.
|
|
|
|
|
* let pvnode = w4.__owl__.pvnode;
|
|
|
|
|
* c1[_2_index] = pvnode;
|
|
|
|
|
* });
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* // we register the deferred here so the parent can coordinate its patch operation
|
|
|
|
|
* // with all the children.
|
|
|
|
|
* extra.promises.push(def3);
|
|
|
|
|
* return vn1;
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
|
2019-05-06 12:34:36 +02:00
|
|
|
QWeb.addDirective({
|
2019-06-20 09:42:33 +02:00
|
|
|
name: "component",
|
2019-06-14 15:40:32 +02:00
|
|
|
extraNames: ["props", "keepalive", "asyncroot"],
|
2019-05-06 12:34:36 +02:00
|
|
|
priority: 100,
|
2019-06-11 15:02:10 +02:00
|
|
|
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addLine("//COMPONENT");
|
2019-05-06 12:34:36 +02:00
|
|
|
ctx.rootContext.shouldDefineOwner = true;
|
2019-05-23 16:14:52 +02:00
|
|
|
ctx.rootContext.shouldDefineQWeb = true;
|
2019-07-09 16:38:30 +02:00
|
|
|
ctx.rootContext.shouldDefineParent = true;
|
2019-05-20 10:45:26 +02:00
|
|
|
ctx.rootContext.shouldDefineUtils = true;
|
2019-05-06 12:34:36 +02:00
|
|
|
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
2019-06-14 15:40:32 +02:00
|
|
|
let async = node.getAttribute("t-asyncroot") ? true : false;
|
2019-05-06 12:34:36 +02:00
|
|
|
|
2019-05-20 10:45:26 +02:00
|
|
|
// t-on- events and t-transition
|
2019-06-03 15:12:21 +02:00
|
|
|
const events: [string, string[], string, string][] = [];
|
2019-05-20 10:45:26 +02:00
|
|
|
let transition: string = "";
|
2019-05-06 12:34:36 +02:00
|
|
|
const attributes = (<Element>node).attributes;
|
2019-06-03 10:10:41 +02:00
|
|
|
const props: { [key: string]: string } = {};
|
2019-05-06 12:34:36 +02:00
|
|
|
for (let i = 0; i < attributes.length; i++) {
|
|
|
|
|
const name = attributes[i].name;
|
2019-06-03 10:10:41 +02:00
|
|
|
const value = attributes[i].textContent!;
|
2019-05-06 12:34:36 +02:00
|
|
|
if (name.startsWith("t-on-")) {
|
2019-06-03 14:28:10 +02:00
|
|
|
const [eventName, ...mods] = name.slice(5).split(".");
|
2019-06-03 15:12:21 +02:00
|
|
|
let extraArgs;
|
|
|
|
|
let handlerName = value.replace(/\(.*\)/, function(args) {
|
|
|
|
|
extraArgs = args.slice(1, -1);
|
|
|
|
|
return "";
|
|
|
|
|
});
|
|
|
|
|
events.push([eventName, mods, handlerName, extraArgs]);
|
2019-05-20 10:45:26 +02:00
|
|
|
} else if (name === "t-transition") {
|
2019-06-03 10:10:41 +02:00
|
|
|
transition = value;
|
|
|
|
|
} else if (!name.startsWith("t-")) {
|
|
|
|
|
if (name !== "class" && name !== "style") {
|
|
|
|
|
// this is a prop!
|
|
|
|
|
props[name] = ctx.formatExpression(value);
|
|
|
|
|
}
|
2019-05-06 12:34:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let key = node.getAttribute("t-key");
|
|
|
|
|
if (key) {
|
|
|
|
|
key = ctx.formatExpression(key);
|
|
|
|
|
}
|
2019-06-03 10:10:41 +02:00
|
|
|
|
|
|
|
|
// computing the props string representing the props object
|
|
|
|
|
let propStr = Object.keys(props)
|
|
|
|
|
.map(k => k + ":" + props[k])
|
|
|
|
|
.join(",");
|
2019-05-06 12:34:36 +02:00
|
|
|
let dummyID = ctx.generateID();
|
|
|
|
|
let defID = ctx.generateID();
|
2019-06-20 09:42:33 +02:00
|
|
|
let componentID = ctx.generateID();
|
2019-05-06 12:34:36 +02:00
|
|
|
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};`);
|
|
|
|
|
}
|
|
|
|
|
ctx.addLine(`let def${defID};`);
|
|
|
|
|
let templateID = key
|
|
|
|
|
? `key${keyID}`
|
|
|
|
|
: ctx.inLoop
|
2019-06-20 09:42:33 +02:00
|
|
|
? `String(-${componentID} - i)`
|
|
|
|
|
: String(componentID);
|
2019-06-23 09:03:18 +02:00
|
|
|
if (ctx.allowMultipleRoots) {
|
|
|
|
|
// necessary to prevent collisions
|
|
|
|
|
if (!key && ctx.inLoop) {
|
|
|
|
|
let id = ctx.generateID();
|
2019-06-28 10:32:03 +02:00
|
|
|
ctx.addLine(`let template${id} = "_slot_" + String(-${componentID} - i)`);
|
2019-06-23 09:03:18 +02:00
|
|
|
templateID = `template${id}`;
|
|
|
|
|
} else {
|
|
|
|
|
templateID = `"_slot_${templateID}"`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-06 12:34:36 +02:00
|
|
|
|
|
|
|
|
let ref = node.getAttribute("t-ref");
|
|
|
|
|
let refExpr = "";
|
|
|
|
|
let refKey: string = "";
|
|
|
|
|
if (ref) {
|
|
|
|
|
refKey = `ref${ctx.generateID()}`;
|
2019-05-28 13:17:38 +02:00
|
|
|
ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`);
|
2019-06-20 09:42:33 +02:00
|
|
|
refExpr = `context.refs[${refKey}] = w${componentID};`;
|
2019-05-06 12:34:36 +02:00
|
|
|
}
|
2019-05-20 10:45:26 +02:00
|
|
|
let transitionsInsertCode = "";
|
|
|
|
|
if (transition) {
|
2019-06-11 14:48:37 +02:00
|
|
|
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
|
2019-05-20 10:45:26 +02:00
|
|
|
}
|
2019-06-28 10:32:03 +02:00
|
|
|
let finalizeComponentCode = `w${componentID}.${keepAlive ? "unmount" : "destroy"}();`;
|
2019-06-06 08:49:29 +02:00
|
|
|
if (ref && !keepAlive) {
|
2019-06-20 09:42:33 +02:00
|
|
|
finalizeComponentCode += `delete context.refs[${refKey}];`;
|
2019-05-06 12:34:36 +02:00
|
|
|
}
|
2019-05-20 10:45:26 +02:00
|
|
|
if (transition) {
|
2019-06-20 09:42:33 +02:00
|
|
|
finalizeComponentCode = `let finalize = () => {
|
|
|
|
|
${finalizeComponentCode}
|
2019-05-20 10:45:26 +02:00
|
|
|
};
|
2019-06-11 14:48:37 +02:00
|
|
|
utils.transitionRemove(vn, '${transition}', finalize);`;
|
2019-05-20 10:45:26 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-06 12:34:36 +02:00
|
|
|
let createHook = "";
|
|
|
|
|
let classAttr = node.getAttribute("class");
|
|
|
|
|
let tattClass = node.getAttribute("t-att-class");
|
|
|
|
|
let styleAttr = node.getAttribute("style");
|
|
|
|
|
let tattStyle = node.getAttribute("t-att-style");
|
|
|
|
|
if (tattStyle) {
|
|
|
|
|
const attVar = `_${ctx.generateID()}`;
|
|
|
|
|
ctx.addLine(`const ${attVar} = ${ctx.formatExpression(tattStyle)};`);
|
|
|
|
|
tattStyle = attVar;
|
|
|
|
|
}
|
2019-06-25 14:24:03 +02:00
|
|
|
let classObj = "";
|
2019-05-31 14:03:34 +02:00
|
|
|
if (classAttr || tattClass || styleAttr || tattStyle || events.length) {
|
2019-05-06 12:34:36 +02:00
|
|
|
if (classAttr) {
|
2019-06-25 14:24:03 +02:00
|
|
|
let classDef = classAttr
|
|
|
|
|
.trim()
|
|
|
|
|
.split(/\s+/)
|
|
|
|
|
.map(a => `'${a}':true`)
|
|
|
|
|
.join(",");
|
|
|
|
|
classObj = `_${ctx.generateID()}`;
|
|
|
|
|
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
2019-05-06 12:34:36 +02:00
|
|
|
}
|
|
|
|
|
if (tattClass) {
|
2019-06-25 14:24:03 +02:00
|
|
|
let tattExpr = ctx.formatExpression(tattClass);
|
|
|
|
|
if (tattExpr[0] !== "{" || tattExpr[tattExpr.length - 1] !== "}") {
|
2019-06-28 15:48:33 +02:00
|
|
|
tattExpr = `utils.toObj(${tattExpr})`;
|
2019-06-25 14:24:03 +02:00
|
|
|
}
|
|
|
|
|
if (classAttr) {
|
|
|
|
|
ctx.addLine(`Object.assign(${classObj}, ${tattExpr})`);
|
|
|
|
|
} else {
|
|
|
|
|
classObj = `_${ctx.generateID()}`;
|
|
|
|
|
ctx.addLine(`let ${classObj} = ${tattExpr};`);
|
|
|
|
|
}
|
2019-05-06 12:34:36 +02:00
|
|
|
}
|
2019-05-31 14:03:34 +02:00
|
|
|
let eventsCode = events
|
2019-06-03 15:12:21 +02:00
|
|
|
.map(function([eventName, mods, handlerName, extraArgs]) {
|
2019-06-21 16:23:42 +02:00
|
|
|
let params = "owner";
|
|
|
|
|
if (extraArgs) {
|
|
|
|
|
if (ctx.inLoop) {
|
|
|
|
|
let argId = ctx.generateID();
|
|
|
|
|
// we need to evaluate the arguments now, because the handler will
|
|
|
|
|
// be set asynchronously later when the widget is ready, and the
|
|
|
|
|
// context might be different.
|
2019-06-28 10:32:03 +02:00
|
|
|
ctx.addLine(`let arg${argId} = ${ctx.formatExpression(extraArgs)};`);
|
2019-06-21 16:23:42 +02:00
|
|
|
params = `owner, arg${argId}`;
|
|
|
|
|
} else {
|
|
|
|
|
params = `owner, ${ctx.formatExpression(extraArgs)}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-03 14:28:10 +02:00
|
|
|
let handler;
|
|
|
|
|
if (mods.length > 0) {
|
|
|
|
|
handler = `function (e) {`;
|
|
|
|
|
handler += mods
|
|
|
|
|
.map(function(mod) {
|
2019-06-20 09:42:33 +02:00
|
|
|
return T_COMPONENT_MODS_CODE[mod];
|
2019-06-03 14:28:10 +02:00
|
|
|
})
|
|
|
|
|
.join("");
|
2019-06-03 15:12:21 +02:00
|
|
|
handler += `owner['${handlerName}'].call(${params}, e);}`;
|
2019-06-03 14:28:10 +02:00
|
|
|
} else {
|
2019-06-03 15:12:21 +02:00
|
|
|
handler = `owner['${handlerName}'].bind(${params})`;
|
2019-06-03 14:28:10 +02:00
|
|
|
}
|
|
|
|
|
return `vn.elm.addEventListener('${eventName}', ${handler});`;
|
2019-05-31 14:03:34 +02:00
|
|
|
})
|
|
|
|
|
.join("");
|
2019-05-06 12:34:36 +02:00
|
|
|
const styleExpr = tattStyle || (styleAttr ? `'${styleAttr}'` : false);
|
2019-05-31 14:03:34 +02:00
|
|
|
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr};` : "";
|
2019-06-25 14:24:03 +02:00
|
|
|
createHook = `vnode.data.hook = {create(_, vn){${styleCode}${eventsCode}}};`;
|
2019-05-06 12:34:36 +02:00
|
|
|
}
|
2019-05-16 14:51:24 +02:00
|
|
|
|
|
|
|
|
ctx.addLine(
|
2019-07-11 14:00:15 +02:00
|
|
|
`let w${componentID} = ${templateID} in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[${templateID}]] : false;`
|
2019-05-16 14:51:24 +02:00
|
|
|
);
|
2019-07-11 16:19:59 +02:00
|
|
|
if (ctx.parentNode) {
|
|
|
|
|
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
|
|
|
|
|
}
|
|
|
|
|
let shouldProxy = false;
|
2019-06-14 10:17:13 +02:00
|
|
|
if (async) {
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addLine(`const patchQueue${componentID} = [];`);
|
2019-06-15 14:47:05 +02:00
|
|
|
ctx.addLine(
|
2019-06-28 10:32:03 +02:00
|
|
|
`c${ctx.parentNode}.push(w${componentID} && w${componentID}.__owl__.pvnode || null);`
|
2019-06-15 14:47:05 +02:00
|
|
|
);
|
2019-06-14 10:17:13 +02:00
|
|
|
} else {
|
2019-07-11 16:19:59 +02:00
|
|
|
if (ctx.parentNode) {
|
|
|
|
|
ctx.addLine(`c${ctx.parentNode}.push(null);`);
|
|
|
|
|
} else {
|
|
|
|
|
let id = ctx.generateID();
|
|
|
|
|
ctx.rootContext.rootNode = id;
|
|
|
|
|
shouldProxy = true;
|
|
|
|
|
ctx.rootContext.shouldDefineResult = true;
|
|
|
|
|
ctx.addLine(`let vn${id} = {};`);
|
|
|
|
|
ctx.addLine(`result = vn${id};`);
|
|
|
|
|
}
|
2019-06-14 10:17:13 +02:00
|
|
|
}
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addLine(`let props${componentID} = {${propStr}};`);
|
2019-05-16 22:56:10 +02:00
|
|
|
ctx.addIf(
|
2019-06-20 09:42:33 +02:00
|
|
|
`w${componentID} && w${componentID}.__owl__.renderPromise && !w${componentID}.__owl__.vnode`
|
2019-06-01 21:44:43 +02:00
|
|
|
);
|
2019-06-28 10:32:03 +02:00
|
|
|
ctx.addIf(`utils.shallowEqual(props${componentID}, w${componentID}.__owl__.renderProps)`);
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addLine(`def${defID} = w${componentID}.__owl__.renderPromise;`);
|
2019-06-01 21:44:43 +02:00
|
|
|
ctx.addElse();
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addLine(`w${componentID}.destroy();`);
|
|
|
|
|
ctx.addLine(`w${componentID} = false;`);
|
2019-06-01 21:44:43 +02:00
|
|
|
ctx.closeIf();
|
2019-05-16 14:51:24 +02:00
|
|
|
ctx.closeIf();
|
|
|
|
|
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addIf(`!w${componentID}`);
|
|
|
|
|
// new component
|
|
|
|
|
ctx.addLine(`let componentKey${componentID} = ${ctx.interpolate(value)};`);
|
2019-05-23 16:14:52 +02:00
|
|
|
ctx.addLine(
|
2019-06-20 09:42:33 +02:00
|
|
|
`let W${componentID} = context.components && context.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];`
|
2019-05-23 16:14:52 +02:00
|
|
|
);
|
2019-05-16 14:51:24 +02:00
|
|
|
// maybe only do this in dev mode...
|
2019-05-16 22:56:10 +02:00
|
|
|
ctx.addLine(
|
2019-06-20 09:42:33 +02:00
|
|
|
`if (!W${componentID}) {throw new Error('Cannot find the definition of component "' + componentKey${componentID} + '"')}`
|
2019-05-16 22:56:10 +02:00
|
|
|
);
|
2019-06-28 15:48:33 +02:00
|
|
|
if (QWeb.dev) {
|
|
|
|
|
ctx.addLine(`utils.validateProps(W${componentID}, props${componentID})`);
|
|
|
|
|
}
|
2019-07-09 16:38:30 +02:00
|
|
|
ctx.addLine(`w${componentID} = new W${componentID}(parent, props${componentID});`);
|
2019-07-11 14:00:15 +02:00
|
|
|
ctx.addLine(`parent.__owl__.cmap[${templateID}] = w${componentID}.__owl__.id;`);
|
2019-06-11 15:02:10 +02:00
|
|
|
|
|
|
|
|
// SLOTS
|
2019-07-05 09:45:55 +02:00
|
|
|
const varDefs: string[] = [];
|
|
|
|
|
const hasSlots = node.childNodes.length;
|
|
|
|
|
if (hasSlots) {
|
|
|
|
|
ctx.rootContext.shouldTrackScope = true;
|
|
|
|
|
for (let v of Object.values(ctx.variables)) {
|
|
|
|
|
if (v["id"]) {
|
|
|
|
|
varDefs.push(v["id"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-15 14:47:05 +02:00
|
|
|
const clone = <Element>node.cloneNode(true);
|
|
|
|
|
const slotNodes = clone.querySelectorAll("[t-set]");
|
2019-06-11 15:02:10 +02:00
|
|
|
const slotId = qweb.nextSlotId++;
|
2019-06-20 09:42:33 +02:00
|
|
|
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
|
2019-06-15 14:47:05 +02:00
|
|
|
if (slotNodes.length) {
|
|
|
|
|
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
|
|
|
|
const slotNode = slotNodes[i];
|
|
|
|
|
slotNode.parentElement!.removeChild(slotNode);
|
|
|
|
|
const key = slotNode.getAttribute("t-set")!;
|
|
|
|
|
slotNode.removeAttribute("t-set");
|
2019-07-05 09:45:55 +02:00
|
|
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
2019-06-15 14:47:05 +02:00
|
|
|
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-24 22:59:13 +02:00
|
|
|
if (clone.childNodes.length) {
|
2019-06-23 09:03:18 +02:00
|
|
|
const t = clone.ownerDocument!.createElement("t");
|
2019-06-24 22:59:13 +02:00
|
|
|
for (let child of Object.values(clone.childNodes)) {
|
2019-06-23 09:03:18 +02:00
|
|
|
t.appendChild(child);
|
|
|
|
|
}
|
2019-07-05 09:45:55 +02:00
|
|
|
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
2019-06-15 14:47:05 +02:00
|
|
|
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
|
|
|
|
}
|
2019-06-11 15:02:10 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-08 16:41:29 +02:00
|
|
|
let scopeVars = "";
|
|
|
|
|
if (hasSlots) {
|
|
|
|
|
scopeVars += ctx.scopeVars.length ? `Object.assign({}, scope)` : varDefs.length ? `{}` : "";
|
|
|
|
|
if (varDefs.length) {
|
|
|
|
|
scopeVars += `, {${varDefs.join(",")}}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-05 09:45:55 +02:00
|
|
|
ctx.addLine(`def${defID} = w${componentID}.__prepare(${scopeVars});`);
|
2019-05-20 10:45:26 +02:00
|
|
|
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
2019-07-11 16:19:59 +02:00
|
|
|
let registerCode = `c${ctx.parentNode}[_${dummyID}_index]=pvnode;`;
|
|
|
|
|
if (shouldProxy) {
|
|
|
|
|
registerCode = `utils.defineProxy(vn${ctx.rootNode}, pvnode);`;
|
|
|
|
|
}
|
2019-05-06 12:34:36 +02:00
|
|
|
ctx.addLine(
|
2019-07-11 16:19:59 +02:00
|
|
|
`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;});`
|
2019-05-06 12:34:36 +02:00
|
|
|
);
|
2019-05-16 14:51:24 +02:00
|
|
|
|
|
|
|
|
ctx.addElse();
|
2019-06-20 09:42:33 +02:00
|
|
|
// need to update component
|
2019-08-28 10:02:38 +02:00
|
|
|
let patchQueueCode = async ? `patchQueue${componentID}` : "extra.patchQueue";
|
|
|
|
|
if (keepAlive) {
|
2019-09-01 11:45:13 +02:00
|
|
|
// 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 ? ${patchQueueCode} : []`;
|
2019-08-28 10:02:38 +02:00
|
|
|
}
|
2019-06-28 15:48:33 +02:00
|
|
|
if (QWeb.dev) {
|
|
|
|
|
ctx.addLine(`utils.validateProps(w${componentID}.constructor, props${componentID})`);
|
|
|
|
|
}
|
2019-05-16 14:51:24 +02:00
|
|
|
ctx.addLine(
|
2019-07-05 09:45:55 +02:00
|
|
|
`def${defID} = def${defID} || w${componentID}.__updateProps(props${componentID}, extra.forceUpdate, ${patchQueueCode}${scopeVars &&
|
|
|
|
|
", " + scopeVars});`
|
2019-05-16 14:51:24 +02:00
|
|
|
);
|
2019-05-16 22:56:10 +02:00
|
|
|
let keepAliveCode = "";
|
|
|
|
|
if (keepAlive) {
|
2019-06-24 13:02:12 +02:00
|
|
|
keepAliveCode = `pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w${componentID}.el,vn.elm);vn.elm=w${componentID}.el;w${componentID}.__remount();};`;
|
2019-05-16 22:56:10 +02:00
|
|
|
}
|
2019-05-06 12:34:36 +02:00
|
|
|
ctx.addLine(
|
2019-06-20 09:42:33 +02:00
|
|
|
`def${defID} = def${defID}.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};${
|
|
|
|
|
tattStyle ? `w${componentID}.el.style=${tattStyle};` : ""
|
2019-07-11 16:19:59 +02:00
|
|
|
}let pvnode=w${componentID}.__owl__.pvnode;${keepAliveCode}${registerCode}});`
|
2019-05-06 12:34:36 +02:00
|
|
|
);
|
2019-05-16 22:56:10 +02:00
|
|
|
ctx.closeIf();
|
2019-05-06 12:34:36 +02:00
|
|
|
|
2019-06-25 14:24:03 +02:00
|
|
|
if (classObj) {
|
|
|
|
|
ctx.addLine(`w${componentID}.__owl__.classObj=${classObj};`);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-14 10:17:13 +02:00
|
|
|
if (async) {
|
|
|
|
|
ctx.addLine(
|
2019-06-24 13:02:12 +02:00
|
|
|
`def${defID}.then(w${componentID}.__applyPatchQueue.bind(w${componentID}, patchQueue${componentID}));`
|
2019-06-14 10:17:13 +02:00
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
ctx.addLine(`extra.promises.push(def${defID});`);
|
|
|
|
|
}
|
2019-05-06 12:34:36 +02:00
|
|
|
|
2019-06-28 10:32:03 +02:00
|
|
|
if (node.hasAttribute("t-if") || node.hasAttribute("t-else") || node.hasAttribute("t-elif")) {
|
2019-05-06 12:34:36 +02:00
|
|
|
ctx.closeIf();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|