Last active
May 17, 2019 08:28
-
-
Save ged-odoo/4f393dabcc7a255c14eff7d107b42d8f to your computer and use it in GitHub Desktop.
Notes on t-widget
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a detailed explanation on the compiled code generated by the | |
// t-widget directive for a given template: | |
// <div><t t-widget="child" t-key="'somestring'" t-props="{flag:state.flag}"/></div> | |
// | |
// Hopefully, this may help someday some poor developer that has to maintain | |
// this code! Good luck, my friend... | |
// This is the virtual node representing the parent div | |
let c1 = [], | |
p1 = { key: 1 }; | |
var vn1 = h("div", p1, c1); | |
// Beginning of the t-widget code | |
// we evaluate 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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment