mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[DOC] component: add detailed explanation for t-widget
This commit is contained in:
+145
-1
@@ -162,6 +162,150 @@ QWeb.addDirective({
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// t-widget
|
// t-widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The t-widget directive is certainly a complicated and hard to maintain piece
|
||||||
|
* of code. To help you, fellow developer, if you have to maintain it, I offer
|
||||||
|
* 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
|
||||||
|
* explanation of the code generated by the t-widget directive for the following
|
||||||
|
* situation:
|
||||||
|
* ```xml
|
||||||
|
* <t t-widget="child" t-key="'somestring'" t-props="{flag:state.flag}"/>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // this is the virtual node representing the parent div
|
||||||
|
* let c1 = [], p1 = { key: 1 };
|
||||||
|
* var vn1 = h("div", p1, c1);
|
||||||
|
*
|
||||||
|
* // t-widget directive: we start by evaluating the expression given by t-key:
|
||||||
|
* let key5 = "somestring";
|
||||||
|
*
|
||||||
|
* // We keep the index of the position of the widget in the closure. We push
|
||||||
|
* // null to reserve the slot, and will replace it later by the widget vnode,
|
||||||
|
* // when it will be ready (do not forget that preparing/rendering a widget is
|
||||||
|
* // asynchronous)
|
||||||
|
* let _2_index = c1.length;
|
||||||
|
* c1.push(null);
|
||||||
|
*
|
||||||
|
* // def3 is the deferred that will contain later either the new widget
|
||||||
|
* // creation, or the props update...
|
||||||
|
* let def3;
|
||||||
|
*
|
||||||
|
* // this is kind of tricky: we need here to find if the widget was already
|
||||||
|
* // created by a previous rendering. This is done by checking the internal
|
||||||
|
* // `cmap` (children map) of the parent widget: it maps keys to widget ids,
|
||||||
|
* // 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;
|
||||||
|
*
|
||||||
|
* // 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 };
|
||||||
|
*
|
||||||
|
* // If we have a widget, currently rendering, but not ready yet, and which was
|
||||||
|
* // rendered with different props, we do not want to wait for it to be ready,
|
||||||
|
* // then update it. We simply destroy it, and start anew.
|
||||||
|
* if (
|
||||||
|
* w4 &&
|
||||||
|
* w4.__owl__.renderPromise &&
|
||||||
|
* !w4.__owl__.isStarted &&
|
||||||
|
* props4 !== w4.__owl__.renderProps
|
||||||
|
* ) {
|
||||||
|
* w4.destroy();
|
||||||
|
* w4 = false;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (!w4) {
|
||||||
|
* // in this situation, we need to create a new widget. First step is
|
||||||
|
* // to get a reference to the class, then create an instance with
|
||||||
|
* // current context as parent, and the props.
|
||||||
|
* let W4 = context.widgets["child"];
|
||||||
|
* if (!W4) {
|
||||||
|
* throw new Error("Cannot find the definition of widget 'child'");
|
||||||
|
* }
|
||||||
|
* w4 = new W4(owner, props4);
|
||||||
|
*
|
||||||
|
* // Whenever we rerender the parent widget, we need to be sure that we
|
||||||
|
* // are able to find the widget instance. To do that, we register it to
|
||||||
|
* // the parent cmap (children map). Note that the 'template' key is
|
||||||
|
* // used here, since this is what identify the widget from the template
|
||||||
|
* // perspective.
|
||||||
|
* context.__owl__.cmap[key5] = w4.__owl__.id;
|
||||||
|
*
|
||||||
|
* // _prepare is called, to basically call willStart, then render the
|
||||||
|
* // widget
|
||||||
|
* def3 = w4._prepare();
|
||||||
|
*
|
||||||
|
* def3 = def3.then(vnode => {
|
||||||
|
* // we create here a virtual node for the parent (NOT the widget). This
|
||||||
|
* // 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
|
||||||
|
* // widget at the proper time
|
||||||
|
* pvnode.data.hook = {
|
||||||
|
* insert(vn) {
|
||||||
|
* // the _mount method will patch the widget vdom into the elm vn.elm,
|
||||||
|
* // 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
|
||||||
|
* let nvn = w4._mount(vnode, vn.elm);
|
||||||
|
* pvnode.elm = nvn.elm;
|
||||||
|
* },
|
||||||
|
* remove() {
|
||||||
|
* // apparently, in some cases, it is necessary to call the destroy
|
||||||
|
* // method here
|
||||||
|
* w4.destroy();
|
||||||
|
* },
|
||||||
|
* destroy() {
|
||||||
|
* // and here...
|
||||||
|
* w4.destroy();
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* // 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
|
||||||
|
* // widget, so we can reuse it later whenever we update the widget
|
||||||
|
* w4.__owl__.pvnode = pvnode;
|
||||||
|
* });
|
||||||
|
* } else {
|
||||||
|
* // this is the 'update' path of the directive.
|
||||||
|
* // the call to _updateProps is the actual widget update
|
||||||
|
* def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
|
* def3 = def3.then(() => {
|
||||||
|
* // if widget was destroyed in the meantime, we do nothing (so, this
|
||||||
|
* // means that the parent's element children list will have a null in
|
||||||
|
* // the widget's position, which will cause the pvnode to be removed
|
||||||
|
* // 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;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "widget",
|
name: "widget",
|
||||||
extraNames: ["props", "keepalive"],
|
extraNames: ["props", "keepalive"],
|
||||||
@@ -285,7 +429,7 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn){let nvn=w${widgetID}._mount(vnode, vn.elm);pvnode.elm=nvn.elm;${refExpr}},remove(){${finalizeWidgetCode}},destroy(){${finalizeWidgetCode}}}});c${
|
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn){let nvn=w${widgetID}._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}},remove(){${finalizeWidgetCode}},destroy(){${finalizeWidgetCode}}}});c${
|
||||||
ctx.parentNode
|
ctx.parentNode
|
||||||
}[_${dummyID}_index]=pvnode;w${widgetID}.__owl__.pvnode = pvnode;});`
|
}[_${dummyID}_index]=pvnode;w${widgetID}.__owl__.pvnode = pvnode;});`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl
|
|||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
def3 = w4._prepare();
|
def3 = w4._prepare();
|
||||||
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.style = _5}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.style = _5}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};w4.el.style=_5;let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};w4.el.style=_5;let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
@@ -62,7 +62,7 @@ exports[`class and style attributes with t-widget t-att-class is properly added/
|
|||||||
if (_5[k]) {
|
if (_5[k]) {
|
||||||
vn.elm.classList.add(k);
|
vn.elm.classList.add(k);
|
||||||
}
|
}
|
||||||
}}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
}}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let cl=w4.el.classList;for (let k in _5) {if (_5[k]) {cl.add(k)} else {cl.remove(k)}}let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let cl=w4.el.classList;for (let k in _5) {if (_5[k]) {cl.add(k)} else {cl.remove(k)}}let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
@@ -109,7 +109,7 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
|
|||||||
w7 = new W7(owner, props7);
|
w7 = new W7(owner, props7);
|
||||||
context.__owl__.cmap[key8] = w7.__owl__.id;
|
context.__owl__.cmap[key8] = w7.__owl__.id;
|
||||||
def6 = w7._prepare();
|
def6 = w7._prepare();
|
||||||
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: key8, hook: {insert(vn){let nvn=w7._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w7.destroy()},destroy(){w7.destroy()}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
|
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: key8, hook: {insert(vn){let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w7.destroy()},destroy(){w7.destroy()}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def6 = w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
def6 = w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
||||||
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;});
|
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;});
|
||||||
@@ -144,7 +144,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[key5] = w4.__owl__.id;
|
context.__owl__.cmap[key5] = w4.__owl__.id;
|
||||||
def3 = w4._prepare();
|
def3 = w4._prepare();
|
||||||
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: key5, hook: {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: key5, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
@@ -177,7 +177,7 @@ exports[`random stuff/miscellaneous t-props should not be undefined (snapshottin
|
|||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
def3 = w4._prepare();
|
def3 = w4._prepare();
|
||||||
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
|
|||||||
Reference in New Issue
Block a user