Before this commit, Owl could not detect that the underlying component
in a template such as <t t-component="{{state.child}}"/> was changing,
if the two components have the same tag as root element.
This is because the reconciliation is done at the vdom level, which does
not know about components. To solve this, one could use a t-key to make
sure owl can make the difference.
With this commit, we can simply use our knowledge of the fact that we are
dealing with a dynamic component and autogenerate a suitable key.
closes#623
the low level method htmlelement.classList.add does not accept multiple
classes in one string, which is why, in owl, the expression
`<div t-att-class="{'a b c': value}" />`
did not work as one might expect. It is however very convenient in real
life templates, so this commit improve owl by adding support for this
feature.
closes#813
Before this commit, the error handling code simply destroyed the
application whenever an unhandled error occured in the owl rendering
process. This is perfectly fine, except that since the application is
potentially corrupted, the destroy code may crash as well. We simply
catch those errors to avoid shadowing the main issue.
closes#866
Before this commit, owl was erroneously defining default slots in most
cases, even though they are empty. The problem occurs when the content
of a component slots is a t-set-slot, and we remove that, then use the
rest as default slots, even though it is only reduced to a set of text
nodes.
With this commit, we only consider the content as default content if it
is not only a sequence of text nodes containing only spaces.
closes#882
The templates contained in a slot have to be rendered with the current
scope, but during the rendering of the sub component, which happens
later (after the willStart for the sub component). Therefore, we need to
save the scope that should be used for the slots, so we can access the
proper variables. This was done using an Object.assign() statement,
which is actually only a shallowclone for the own properties.
In this commit, we properly copy all the properties for the current
scope, even those contained in the prototype.
closes#855
Before this commit, it was not possible to set the value of a select tag
by using the t-att-value attribute.
Doing so is not actually trivial because of the way the vdom works: it
processes the node attributes before its children are created, which
means that the vdom code tries to set the initial value of the select
before its children are created, which means that it is ignored. To make
it work, I added a node create hook which is called after the children
are completely processed.
closes#873
Before this commit, using an expression such as "{machin}" was compiled
in qweb into "{scope['machin']}" which is not valid.
With this commit, we instead transform it into "{machin:
scope['machin']}".
closes#885
Before this commit, props and t-props were computed like
`Object.assign(props, t_props)`.
Now, it computes like `Object.assign(t_props, props)` so props will
override t-props.
closes https://github.com/odoo/owl/issues/886
Before this commit, the error handling process was too naive: once an
error occurs in a rendering, owl catches it, looks for a component that
implements the catchError method, then calls it.
However, in real life, we sometimes need to rethrow that error (or
another one) to propagate the error to some parent handler. This error
needs to be handled by the closest parent component that implements
catchError.
This is what this commit implements: it wraps the catchError call in a
try/catch, then in case of errors, try to handle it by a parent.
Have a t-call within a t-set-slot of a component.
The called template has a t-component directive.
It should be like:
```xml
<t t-name="Zero">
<Slotted>
<t t-call="someTemplate" />
</Slotted>
</t>
<t t-name="someTemplate">
<SomeComponent />
</t>
```
Before this commit, the parent of SomeComponent was the Zero component
After this commit, the parent of SomeComponent is the Slotted Component as it should be
closes#862
Before this commit, params in paths had to be be between slashes and
comprise the entirety of the contents between those slashes (eg:
`/books/{{id}}/{{name}}`)
This is unnecessarily restrictive. This commit removes this restriction,
which allows for paths such as:
- `/books/{{id}}-{{name}}`
- `#books&id={{id}}&name={{name}}`
among others
closes#858
Previously, the order of resolution was:
- components defined on the class of the current component
- components defined on the QWeb instance
- the current instance's context
This is unnatural because it doesn't go from most specific to least, as
the current instance's context is the most specific. This is also
fragile, as adding components to the QWeb instance can break unrelated
components.
This commit fixes that by making the lookup start with the current
component's context instead
The initial problem solved by this commit is that it was possible to get
into a situation where a mounting/rendering was started, then the component was
updated, but then another mounting operation begins, and it tries to
reuse the previous rendering operation, which is no longer uptodate.
The underlying issue is that Owl did not track properly the various
internal state change of a component. These issue should be solved by
the introduction of the status enum, which currently tracks 6 possible
states:
- CREATED
- WILLSTARTED
- RENDERED
- MOUNTED
- UNMOUNTED
- DESTROYED
This status number replaces the isMounted and isDestroyed boolean flags.
It has the advantage of making sure that the component is in a
consistent state (it is no longer possible to be destroyed and mounted,
for example)
Another advantage is that it gives us an easy way to track the fact that
a component has been rendered, but is not in the DOM. This is a subtle
situation where some various events can happen, and we need to be able
to react to that case.
Note that there is a change of behaviour: if a component is mounted in a
specific target, then before the mounting is complete, the component is
mounted in another target, we no longer reject the first mounting
operation.
The QWeb expression parser use an object as a mapping between some
strings and the desired output in the compiled template. However, as we
should all know, objects are not Maps, they have some additional
properties, such as "constructor" or "hasOwnProperty".
The simple solution is to make sure the mapping object does not have
anything in its prototype chain to pollute its purpose.
closes#835
From the beginning, Owl was designed to only call event handler on
components that are mounted in the DOM. The main benefit is that if a
component is destroyed, we are guaranteed to not execute any useless (or
potentially dangerous code).
However, there is one downside: if a component tree is being mounted,
and a child component trigger an event in its mounted hook, then it
cannot be caught by the parent, since the parent is technically not yet
mounted.
This may not be a good situation, but the point is that Owl
unnecessarily prevent the handler to be called.
We can fix this issue by simply checking if the component is not destroyed
instead of checking that it is mounted.
closes#809
This commit makes sure that Owl does not crash when a component is
created, then updated (for example, with a (observed) state change), and
then, some moment later, mounted.
The initial render is not useful, because it is not linked to a mounting
action anyway. And it caused issues such as a crash when Owl tried to
patch it to a non existing target
This commit tries to improve the interactions involving unmounted
components, or components mounted in an htmelement which is detached
from the main DOM, and rendering actions.
The main example is mounting a component in detached div, to prepare all
children. If we just mount the component, it will work as expected: the
full component tree is rendered in memory, and ready to be really
mounted at the desired target.
However, if before doing that, we update the component and call render
on it (for example, with a change in an observed state), then this
rendering will be ignored, and therefore, the full subcomponent tree is
not uptodate.
This commit will also solve another issue in the compatibility layer in
odoo: in the form renderer, we mount components with the adapter in a
div, which is not yet attached to the DOM. We then manually call the
mounted hook when on_attach_callback is called. This means that before
this commit, any change to the components between the initial rendering
and the call to mounted will be ignored.
As a bonus, this commit has the effect of bringing closer the semantics
of render and mount operations, which is certainly good.
closes#823
Before this commit, the following scenario could happen:
Suppose that we have a parent component A, connected to a store,
and a child component B, also connected to the store and using
the onUpdate feature.
Then, we remount the A component in some other places and a
rendering is initiated in A. We immediately update the store state.
What happens next is:
- rendering A is done, A internal revid is updated
- store update A (but nothing is done because the state change here
does not modify A)
- rendering B is done (from parent), B internal revid is updated
- store update B, notice internal revid is updated, does not call the
onUpdate function
We then have the B component which has not its internal state updated,
because we did not call its onUpdate function.
The solution is to move the onUpdate call in a "preupdate" event, to be
sure that it is called everytime the store is updated.
closes#816
In some cases, a rendering initiated in some component is then remapped
into a larger rendering initiated by some parent.
If we have some components which implement shouldUpdate to return false,
then the following scenario can happen:
- some parent component is mounted (which triggers a rendering with
force: true => bypass the shouldUpdate)
- some sub component is updated and rerendered, AFTER the previous
rendering goes through it
- the sub component notices that there is an ongoing rendering, and
remaps itself in the parent rendering
Before this commit, the new fiber in the subcomponent does not have
force flag set to true, so the new rendering for the subcomponent does
not go through its own children (if they have shouldUpdate=false)
Another more complex kind of scenaria can happen when a remapped
rendering happen with sub components with dynamic shouldUpdate. The
problem is the same at the end: the new rendering should ignore the
shouldUpdate, to make sure we have the last correct information.
With this commit, we make sure that the flag of the new fiber is set to
true.
closes#818
Before this commit, an unwanted behaviour happened when using components
with shouldUpdate implemented, and store/state.
The actual problem is the following: the sub component is using a store,
and shouldupdate. Whenever the component is rendered, it checks if there is an incoming
rendering from the context. If that is the case, it skips the
rendering, because we actually only want to be rendered by the store
rendering (otherwise, we may run into issue with inconsistent data (more
recent data from the context, older data in the component).
However, if shouldUpdate is implemented, then the rendering coming from
the context simply does not arrive.
To fix this, we tried to just force these renderings to go through all children,
regardless of their shouldUpdate status. This actually works, but then
shouldUpdate is ignored, which is an issue in Odoo discuss.
Then, after discussing this situation, we noticed that the context/store
system is actually unsafe: the protection given by the check mentioned
above is in fact fundamentally insufficient: there are other perfectly
valid situations where a rendering can be triggered on the component,
which will bypass the check (for example, an explicit call to
this.render() or a rendering initiated by some parent component) and
cause a crash if the component is not properly defensively written.
Therefore, it is currently mandatory for all components using
context/store to be aware of that, and to protect themselves against
such situations. So, in that regard, the check is not really a
protection, it just helps hiding an unsafe situation anyway and we
decided to remove it.
Note that this is a potentially breaking change: components using a
store and some local state will now be rendered twice in some cases...
closes#799
Components can implement shouldUpdate to return false. In that case,
renderings coming from above should be ignored.
However, if the component was unmounted and is remounted, we actually
need to force a rerendering in that case, so it is mounted, otherwise
the subcomponent is left in unmounted state, which means that rendering
are ignored.
closes#800
The reviewer for the commit on named slots inside named slots did not
notice that there was an infinite loop. Because of his sloppiness, Owl
could block in an infinite loop when a named t-slots was defined inside
a subcomponent, but not as a direct child.
Since commit 7e4baf6, some tests were failing non deterministically. It
seems like the root cause for the problem is that the nextTick method
was sometimes completed too early.
From my understanding of the problem, the nextTick method was correct,
however it is running in a jest test, in node, which simulates the
requestanimationframe behaviour, probably not perfectly.
Switching to an alternate implementation, which swaps the setTimeout and
the requestanimationframe seems to be more solid.
Note that this commit had to introduce the nextFrame method as well,
because the animation tests really seems to want to be in the next
frame, not in a callback executed in the same frame, but at the end.
closes#757
Whenever a top level t-call was made with some non empty body, Owl
complained that a template should not have more than one root node.
The reason was that the compilation context for the body of the t-call
directive was the same as the root compilation context, and it already
had a parentNode set.
To fix the issue, this commit simply use the subContext method to create
a different compilation context, which actually makes sense, because the
body of a t-call is really a different situation. Also, as a bonus, it
slightly improves the code for the t-call directive.
closes#760
Strangely, the _compile method required a CompilationContext whenever it
was compiled as a sub template, but this parent context was actually not
really needed. I guess that it was the case in the past, but this was
changed at some point.
This commit makes another significant change: the xml element is no
longer mandatory. It is actually only required for slots (because the
template is not registered to qweb).
Finally, the interface for the whole method has been changed to use an
option object, which makes more sense with 3 optional paremeters.
Previous code naively handled nested t-set-slots: if a second named
slots was found, it overrode the first.
In this commit, we use a set to make sure that we only use the first
found t-set-slot node. Also, we ignore set-slots defined in a sub
components, because these slots are only relevant to the sub component
itself.
Note that it works as expected because document.querySelectorAll
performs a search depth first, so we will always use the named slots
closer to the parent element, in term of depth.
closes#682
Before this commit, the t-model directive worked well with expressions
such as "state.value", but not with bracketed expression: "state[value]"
(it generated invalid code).
This commit make the t-model smarter by detecting this case, and
properly capturing the base expression and key variable.
closes#694
Input with type="checkbox" have a special property (indeterminate) to
visually display the fact that the input value is non determinate (in my
chrome browser, the checkbox is then drawn with a simple - inside). It
does not actually modify the value of the input, only the way it is
displayed.
So, with this commit, owl will properly set the property, as expected.
closes#713
This is a rarely (if ever) used feature, but according to our qweb
reference implementation, it is possible to use the
t-call directive on an arbitrary html tag, like this:
<div t-call="my.template"/>
It is then interpreted as:
<div><t t-call="my.template"/></div>
So, with this commit, we make sure that the owl qweb implementation
matches that behaviour.
closes#706
Promises are kind of special, and do not behave like usual javascript
values.
For this issue, the problem is that when the observer tries to observe a
promise value, the code will crash with an error like this:
Uncaught TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]
Also, note that it does not make much sense to proxify promise methods
anyway, since they are not (supposed) to be modified.
So, with this commit, we simply consider that promises should be treated
like a primitive value: simply ignored when determining if it should be
proxified
Note that I actually believe that putting promises in a useState is not
a good idea in general.
closes#677
The previous fix (overriding tostring of VDomArray) is actually more
general, and solves the same issue. So, let us simplify the code and
keep the more general solution.
This reverts commit 3bf91afc3f.
Consider this scenario:
- a variable v (with a body) is defined in a template
- it is then passed to a sub component as a prop
- and now, it is t-esc-ed.
Before this commit, the displayed value was [object object], because the
value actually passed to the sub component was a VDomArray (internal
structure used to represent nodelists)
This issue is actually quite a problem in practice, because values in a
templates are translated, but not in attributes. Therefore, using a
t-set directive with a body text content is the proper way to have
translated values at runtime.
We override in this commit the method toString of VDomArray to make sure
it is properly displayed.
Note that we considered changing the way props were generated (by trying
to detect VDomArray, then calling vDomToString), but then the value
would not be able to be used in a t-raw. Also, it is quite elegant to
be able to format the VDomArray only at the end.
closes#670
Before this commit, we artificially replaced in the tests the
requestAnimationFrame by a setTimeout, to actually increase the speed of
the tests. However, this is not really a true replacement. For
example, a real setTimeout can come before or after a real
nextAnimationFrame, depending on when/where it is requested.
Also, this change exposed another problem: the nextTick function did a
setTimeout before a nextanimationframe. This is not a problem when
nextAnimationFrame is replaced by a setTimeout, because then all
expectations holds in owl. However, it is wrong: to get to the next
animation frame, we need to request an animation frame, and THEN wait
with a setTimeout.
closes#729
Because of a "break" statement instead of "continue", the check for valid
props was stopping as soon as it met an optional props, which kind of
invalidate the whole system.
closes#717
Sometimes, HTML is slightly more subtle than what I initially expect.
Rendering some html is simple, we have tags and attributes. However,
once we add behaviour, then the situation is more complex:
<input value="abc"/>
is an input with an INITIAL value of "abc", but the attribute does not
actually represent the CURRENT value of the input, which may be
different if the user did change it.
This is basically the difference between "attribute" and "property".
So, when rendering html with owl, we sometimes want to actually set
the property (current value), instead of the html attribute.
This commit make sure that this is the case for inputs with the "value"
attribute.
closes#722
The body of a t-call directive may be used to define private variables
to the sub template call.
However, the code that handles t-call worked like this:
- compile sub template if necessary
- then compile body of t-call to extract variables
This means that the variables defined in the t-call body were not yet
processed and available in the context. Because of that, when the call
to t-esc is done, there is not internal qweb var, and the code simply
outputs a scope['varname'], which is in our case a VDOMArray, so it is
displayed as [object object]
What this fix does is changing the way t-esc works: if we are in the
context of a sub template, then it assumes that any outside variable may
or may not be a VDomArray, so it needs to check and eventually convert
it to a string, if necessary.
closes#719