Compare commits

..

255 Commits

Author SHA1 Message Date
Géry Debongnie e4fdd32f22 [REL] v2.0.0-beta.2
# 2.0.0-beta.2

- only log dev message once, instead of once per app
- add subscriptions getter in dev mode on component node
- fix: issue with missing renderings
- fix: env now preserves prototype chain
2022-03-03 16:21:57 +01:00
Géry Debongnie 8d1d0a2244 [FIX] app: make sure we maintain the correct prototype chain in env 2022-03-03 16:19:05 +01:00
Géry Debongnie 0457e5d4ed [FIX] component: missing renderings in some cases 2022-03-03 16:19:05 +01:00
Samuel Degueldre 8920b4b93a [IMP] component, reactivity: add subscription getter in dev mode
This allows to see which objects and which keys in those objects a
component is observing, so that issues with missing renders or extra
renders can be more easily diagnosed.
2022-03-03 10:33:39 +01:00
Géry Debongnie 6908102a72 [IMP] app: only log dev message once
Before this commit, it was logged for every app created, which is
annoying in odoo: because of the compatibility layer, there are many
apps being created.
2022-03-03 08:48:37 +01:00
Géry Debongnie d6348b8310 [REL] v2.0.0-beta.1
# 2.0.0-beta.1

First beta release! The last missing feature has been merged (support for
sets/maps/weakmaps in the reactivity system).
2022-03-02 13:28:50 +01:00
Géry Debongnie 79738e00c7 [REF] small cleanup for template helpers 2022-03-02 13:26:22 +01:00
Géry Debongnie 2a1b99be2d [REM] remove the Memo component
With the new fine grained reactivity system, it was no longer useful.
2022-03-02 13:26:22 +01:00
Géry Debongnie 8a472231cf [FIX] release script was using a hardcoded value for notes
instead of the value that was actually provided by the user
2022-03-02 13:26:22 +01:00
Géry Debongnie bb373e6a7a [DOC] add explanation on structure of compiled template 2022-03-01 15:46:09 +01:00
Samuel Degueldre d735213758 [IMP] reactivity: add support for collections (Set/Map WeakSet/WeakMap) 2022-02-28 15:43:41 +01:00
Géry Debongnie 076b0d774e [REL] v2.0.0-alpha.3
# v2.0.0-alpha.3

A new release, with the fine grained reactivity applied to component props,
and changes in the way Owl render subcomponents: it can now skip patching and
or rendering child components if needed.
2022-02-25 10:57:25 +01:00
Géry Debongnie f405fe9323 [DOC] add information about reactivity 2022-02-25 10:32:25 +01:00
Géry Debongnie 1ae9d514b9 [IMP] component: use reactivity to allow shallow renderings
With this commit, component only render child
components if they have different props (shallow
equality). Otherwise, we trust the reactivity
system to make sure that all impacted components
are updated
2022-02-25 10:32:25 +01:00
Géry Debongnie 592d9a458e [REF] move useState into component_node.ts 2022-02-25 10:32:25 +01:00
Géry Debongnie 0dbd2bd463 [REF] component: slightly simplify fiber code 2022-02-18 16:21:34 +01:00
Géry Debongnie 8ec7a6f9bf [FIX] compiler: prevent block- attributes and tags 2022-02-16 17:32:59 +01:00
Géry Debongnie 12b8ce963e [IMP] reactivity: toRaw now works with non reactive objects 2022-02-16 09:35:36 +01:00
Géry Debongnie bb9d65e95b [IMP] app: better error message when missing template 2022-02-16 09:35:36 +01:00
Géry Debongnie 73c339fff1 [DOC] remove outdated event bus doc 2022-02-15 10:31:03 +01:00
Géry Debongnie 406be446a5 [FIX] app: display correct url in dev mode message
closes #850
2022-02-15 10:15:07 +01:00
Géry Debongnie bd2aa8a72f [MISC] playground: improve benchmarking code 2022-02-15 09:07:32 +01:00
Géry Debongnie 3536f41f00 [FIX] blockdom: undefined properties are treated as empty strings 2022-02-15 09:07:32 +01:00
Samuel Degueldre 804ad3c35e [IMP] component: improve errors when thrown from lifecycle hooks
Previously, a crash in a lifecycle hook for any reason would throw an
error whose stack trace started from the scheduler and contained only
the place where the hook was called by owl, but not the place where the
hook was registered by the user. This proved very difficult for users to
debug as they cannot really tell which component registered that hook.

This commit alleviates the issue by creating a new Error when the hook
is originally called, and wrapping the registered callback in a try
catch, throwing an error with the correct stack trace instead of the
error in the hook, and setting the error in the hook as the cause of
this synthetic error.
2022-02-14 13:54:17 +01:00
Géry Debongnie 4a922ed82d [REL] v2.0.0-alpha.2
# v2.0.0-alpha.2

A new release, with some small fixes. Owl 2.0 is getting close.
2022-02-14 13:42:34 +01:00
Géry Debongnie a6f0985d43 [TOOLING] add benchmarking code to playground samples 2022-02-14 13:10:30 +01:00
Aaron Bohy 6aee1355c8 [IMP] deploy.yml: remove owl-next from the branch list 2022-02-11 11:03:23 +01:00
Géry Debongnie 921ced7c90 [FIX] refs in recursive templates now work properly 2022-02-11 10:46:44 +01:00
Géry Debongnie 1da930cb25 [MISC] tools: fix version string 2022-02-11 10:46:44 +01:00
Bruno Boi cc8e11c9c9 [FIX] can use t-out with String classes 2022-02-11 10:46:44 +01:00
Géry Debongnie 3196b585fd [FIX] slots: process slot params/values like normal props 2022-02-11 10:46:44 +01:00
Géry Debongnie ea2ccc5a03 [DOC] improve changelog 2022-02-11 10:46:44 +01:00
Géry Debongnie 960808aeb2 [IMP] blockdom: apply dynamic part of block before inserting it 2022-02-11 10:46:44 +01:00
Géry Debongnie 1fb1d37e32 [IMP] props validation: cannot set default value on mandatory props 2022-02-11 10:46:44 +01:00
Géry Debongnie 24ce8613c5 [DOC] add some info to the changelog 2022-02-11 10:46:44 +01:00
Géry Debongnie 2c1226d737 [IMP] compiler: translatableAttributes can be added/removed 2022-02-11 10:46:44 +01:00
Samuel Degueldre 140818b5f9 [IMP] compiler: allow to declare default slot scope on component 2022-02-11 10:46:44 +01:00
Géry Debongnie 83de53d283 [REF] allow external code to override validateTarget 2022-02-11 10:46:44 +01:00
Géry Debongnie 50aac42bdc [IMP] component: validate mounting target at patch time 2022-02-11 10:46:44 +01:00
Géry Debongnie bd98d4d0d0 [IMP] component: disallow calling hooks outside of setup
(and constructor)

Doing so could cause strange and difficult bugs
2022-02-11 10:46:44 +01:00
Bruno Boi 4d68dac24d [FIX] hooks: useSubEnv will not erase previous useChildSubEnv 2022-02-11 10:46:44 +01:00
Géry Debongnie 722abd6d5f [IMP] app: introduce test mode
Same as `dev` mode, but without warning in console
2022-02-11 10:46:44 +01:00
Géry Debongnie a7305a5cdb [TEST] component: add test to make sure a specific issue does not arise 2022-02-11 10:46:44 +01:00
Géry Debongnie ff734c706c [IMP] typing: make app and mount method properly generic 2022-02-11 10:46:44 +01:00
Bruno Boi add5fdd737 [FIX] compiler: never add _ prefix to non variable token 2022-02-11 10:46:44 +01:00
Samuel Degueldre a221411938 [FIX] compiler: do not pass dynamic props object as is
The child receiving the props can observe changes made to the passed
t-props object which is not desirable.
2022-02-11 10:46:44 +01:00
Bruno Boi bb6479f44f [FIX] compiler: add _ prefix to local variables while compiling an expression 2022-02-11 10:46:44 +01:00
Géry Debongnie aa95149997 [IMP] hooks: introduce useChildSubEnv and change useSubEnv 2022-02-11 10:46:44 +01:00
Géry Debongnie e4b4ee471f [FIX] compiler: does not modify xml doc in place 2022-02-11 10:46:44 +01:00
Bruno Boi 3af5e57825 [FIX] component: properly capture expression of t-model 2022-02-11 10:46:44 +01:00
Géry Debongnie 979712f84e [DOC] fix error in slot documentation 2022-02-11 10:46:44 +01:00
Géry Debongnie 93f2c1d766 [FIX] reactivity: clear callbacks at destroy time instead of unmount 2022-02-11 10:46:44 +01:00
Géry Debongnie 0728c8333d [FIX] compiler: add missing ; in some places 2022-02-11 10:46:44 +01:00
Géry Debongnie 6639d361c3 [FIX] component: proper error message in dev mode in some cases 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 72962f1dd1 [FIX] compiler: force new block for svg nested in html 2022-02-11 10:46:44 +01:00
Géry Debongnie a4d9aae9a7 [FIX] svg: allow path as root tag 2022-02-11 10:46:44 +01:00
Samuel Degueldre 1e8576ad40 [FIX] blockdom: fix VHtml patching not setting its html correctly 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 753d82149e [FIX] compiler: t-model on select with options with dynamic values 2022-02-11 10:46:44 +01:00
Géry Debongnie 700030cc7d [DOC] update changelog renderToString example 2022-02-11 10:46:44 +01:00
Géry Debongnie d828f39a2d [FIX] reactivity: do not observe eventtarget and other stuff 2022-02-11 10:46:44 +01:00
Géry Debongnie 0a73154985 [IMP] reactivity: introduces markRaw and toRaw functions 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) d88eb34d4f [FIX] compiler: svg in new block takes the right namespace 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) a8d88d4009 [FIX] compiler: t-key on a sub-domnode pushes an anchor in parent block 2022-02-11 10:46:44 +01:00
Géry Debongnie 6d9ed0d62f [REF] utils: move batched from reactivity to utils 2022-02-11 10:46:44 +01:00
Géry Debongnie b33471e819 [IMP] component: improve error message when invalid handler 2022-02-11 10:46:44 +01:00
Géry Debongnie 3fb65b3a89 [FIX] doc: fix broken link, and fix doc link checker test 2022-02-11 10:46:44 +01:00
Géry Debongnie 09d192999a [DOC] update changelog (add browser removal to changes) 2022-02-11 10:46:44 +01:00
Géry Debongnie 466cf50b73 [FIX] playground: update window management example 2022-02-11 10:46:44 +01:00
Géry Debongnie 96620d3e8e [FIX] scheduler: make sure raf is bound to window 2022-02-11 10:46:44 +01:00
Géry Debongnie 176c89b278 [REF] scheduler: capture requestAnimationFrame asap
This is useful to prevent interactions with other testing code.
2022-02-11 10:46:44 +01:00
Géry Debongnie 38941bc26f [FIX] playground: update example to owl 2 2022-02-11 10:46:44 +01:00
Géry Debongnie ce8ddd1cbf [DOC] improve useEffect doc 2022-02-11 10:46:44 +01:00
Géry Debongnie 81f44ee5d3 [FIX] reactivity: export Reactive type 2022-02-11 10:46:44 +01:00
Géry Debongnie 99b5e9ec55 [FIX] typing: Component class should be generic on Props and Env
Otherwise, it prevents proper typing with typescript
2022-02-11 10:46:44 +01:00
Géry Debongnie 4f35f03986 [DOC] update changelog content 2022-02-11 10:46:44 +01:00
Géry Debongnie eab0caa6cb [IMP] portal: ensure that destroy is synchronous 2022-02-11 10:46:44 +01:00
Géry Debongnie 5a2c769eab [DOC] document synthetic events 2022-02-11 10:46:44 +01:00
Géry Debongnie 89d63ff29a [IMP] components: crash when using unknown suffix/modifiers 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) a2e8abc243 [FIX] component, error_handling: do not cancel the error fiber twice 2022-02-11 10:46:44 +01:00
Géry Debongnie dfd0dcedb8 [IMP] doc: add changelog to doc test, update changelog 2022-02-11 10:46:44 +01:00
Géry Debongnie ad743c205c [IMP] ci: fails if circular dependencies are found in build 2022-02-11 10:46:44 +01:00
Géry Debongnie 7c78442e43 [FIX] remove circular dependency 2022-02-11 10:46:44 +01:00
Géry Debongnie 0d13c362d3 [DOC] remove reference to catchError, fix mistake in changelog 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 374dbb2fd9 [FIX] components: cascading render after microtaskTick (makeChildFiber)
Co-authored-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: François Georis <fge@odoo.com>
2022-02-11 10:46:44 +01:00
Géry Debongnie 2a5f37cf4b [DOC] update changelog 2022-02-11 10:46:44 +01:00
Géry Debongnie cfb6b9f958 [DOC] update translations page 2022-02-11 10:46:44 +01:00
Géry Debongnie 82f6923a21 [DOC] add more information to the slots page 2022-02-11 10:46:44 +01:00
Michael (mcm) cccb379377 [FIX] bind lifecycle callbacks to component
Before this commit, some of the callbacks were bound to the component
and some were not.
This commit makes all the callbacks bind to the component.
2022-02-11 10:46:44 +01:00
Géry Debongnie 82c7c24438 [DOC] reorganize and update documentation to owl 2 2022-02-11 10:46:44 +01:00
Jorge Pinna Puissant 41ad5db2e3 [IMP] portal: compile t-portal in an internal Component Portal
This commit also clean-up the deepRemove for the Portal that is not
needed any more.
2022-02-11 10:46:44 +01:00
Géry Debongnie 42a140a8e3 [FIX] components: only call handlers if component is mounted 2022-02-11 10:46:44 +01:00
Géry Debongnie 7711733a23 [FIX] blockdom: toString method in multi could crash 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 281b32965e [FIX] app: t_call recursive template is bound to the correct this 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) cec451fd15 [FIX] compiler: slot are called with a specific and different key 2022-02-11 10:46:44 +01:00
Samuel Degueldre 6a7703ea82 [FIX] code_generator: stop matching other variables prefix
Previously, we would replace block declarations in some contexts with
the variable alone, and decalres the variable higher in the generated
code. Issues arise because whe sometimes try to replace "let b2" with
"b2" but end up matching "let b20" which is incorrect.

This commit fixes that by adding a space to the text that we are trying
to match ("let b2 " won't match "let b20")
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 4a03a60084 [FIX] component, fiber: subchildren should also patch and destroy their children
Have a GrandParent which controls whether one of its GrandChildren is displayed or not.
First, the GrandChild is displayed. Then, change the state of the GrandParent in order to kill
the GrandChild.

Before this commit the GrandChild is only removed from the DOM, as bdom correctly works.
But it is not destroyed.

After this commit, the GrandChild is correctly destroyed.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) a6bdca082a [FIX] components tests: place tests in right submodule 2022-02-11 10:46:44 +01:00
Jorge Pinna Puissant 90167c5436 [IMP] portal: portal as a Directive
Before this commit, portal was a Component, now is a directive.
This commit also clean some unused code, and fix an issue on the clean
optimization when a portal is found in a condition or a loop.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) c221721d7f [FIX] components: avoid leaks when children are outdated/destroyed
Every use case involving some sort of key set on a component would give birth to a leak in an async context:
- If a key of a component changed, the outdated one was never destroyed.
- destroyed component were never removed from their parent's reference map.

This commit solves both issues, that are tightly linked anyway.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 38f39b6755 [FIX] test/helpers: useLogLifeCycle supports custom key 2022-02-11 10:46:44 +01:00
Géry Debongnie 1775467434 [IMP] tooling: add testTimeout argument to test:debug command 2022-02-11 10:46:44 +01:00
Géry Debongnie 52d0526ddd [FIX] portal: properly handle errors
Before this commit, Portal overrode the _render function for its
component node, which means it bypassed the error handling mechanism
that was implemented in that method.  It could have been fixed by
duplicating the error handling code as well, but a better solution in my
opinion is to simply override the renderFn function.  This is closer to
the actual intent of the portal implementation: wrap the result of the
rendering in a VPortal vnode.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 4b170b9b45 [FIX] app, components: dynamic t-call should propagate the key
Have a Component which has a Component node, and a dynamic t-call itself having
a Component node.

Before this commit, both children had the same `key`, (as in the key in parent.children, which registers on the parent all its children).

As a result, the scheduler was endlessly hanging.

After this commit, it works as expected.
2022-02-11 10:46:44 +01:00
Géry Debongnie 3e1fe07ba7 [REF] compiler: factorize a common pattern 2022-02-11 10:46:44 +01:00
Géry Debongnie 5bf47500d5 [FIX] compiler: handle t-set as functions 2022-02-11 10:46:44 +01:00
Géry Debongnie 75ad0835e9 [DOC] update quick_start and how to test pages 2022-02-11 10:46:44 +01:00
Géry Debongnie 3d6a5eb828 [REM] doc: remove overview page 2022-02-11 10:46:44 +01:00
Géry Debongnie aceaeef8cc [DOC] update the tutorial todo app 2022-02-11 10:46:44 +01:00
Géry Debongnie 06fc3a2c77 [FIX] reactivity: work even if no callback is given 2022-02-11 10:46:44 +01:00
Géry Debongnie 92cc4375f8 [FIX] components: make sure t-ref work with t-if/t-else 2022-02-11 10:46:44 +01:00
Samuel Degueldre 9f2e2bcc66 [IMP] compiler: scope generated ids to their prefix
This means that unrelated ids (eg the id of a template, variable or key)
not longer share the same incrementing counter, meaning that you no
longer see a variable named "v2" unless another variable "v1" was
generated previously, this is also true for block data.
2022-02-11 10:46:44 +01:00
Géry Debongnie c7af885f43 [IMP] compiler: improve generated compiled code 2022-02-11 10:46:44 +01:00
Géry Debongnie bd5637c0a3 [FIX] useEffect: can depend on dom dependencies
Because the dependencies are now computed in patched.
2022-02-11 10:46:44 +01:00
Géry Debongnie 3c98ef8cb1 [FIX] portal: do not crash in dev mode
Before this commit, the props validation would fail in dev mode because
it did not expect a slot prop.
2022-02-11 10:46:44 +01:00
Géry Debongnie aad6b806ba [IMP] app: improve API, small refactoring 2022-02-11 10:46:44 +01:00
Géry Debongnie 772c275bd4 [REF] build: do not output const enum definitions
We only use 2 const enums in the codebase, but they are defined in the
output, even though this is not useful in any way. This commit reduces
the final output by about 30 loc.
2022-02-11 10:46:44 +01:00
Géry Debongnie 416deeb865 [REM] component: remove support for css tag 2022-02-11 10:46:44 +01:00
Géry Debongnie 7e40fa300a [IMP] components: improve props validation
to be able to specify that additional props are allowed
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) cc1eea0945 [FIX] component: error_handling when an error is rethrown 2022-02-11 10:46:44 +01:00
Géry Debongnie bf9cceb56f [REF] components: remove .el 2022-02-11 10:46:44 +01:00
Géry Debongnie 7eaecac0b5 [REF] tests: improve test helpers
- remove snapshotApp
- remove addTemplates
- simplify helpers
- make sure snapshotted templates are snapshotted with the app config
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) ddc358f48a [FIX] fiber, lifecycle: trigger a render during the fiber.complete
Have a component which does a render in its onWillPatch, onPatched, onMounted hooks.

Before this commit, the result was incorrect: the second rendering was not taken into account.

After this commit, those renderings are correctly applied at the price of a delayed render when the fiber
is in a critical state.
2022-02-11 10:46:44 +01:00
Géry Debongnie e2819323ee [FIX] blockdom: properly handle falsy attributes
This commit fixes some issues with falsy attributes not being properly
set/removed in various situations. Also, the behaviour was not
consistent between normal attribute (key/value) and generic attributes
(pair or object)
2022-02-11 10:46:44 +01:00
Géry Debongnie 983b9f996d [FIX] components: improve error handling
Owl provides a way to manage errors occuring in component lifecycle
methods. However, before this commit, these errors were not always
logged or visible, which is very annoying in the common developer
workflow (doing something, checking it works, seeing no error but a
broken interface).

In this commit, we make sure errors are logged/throws in all cases:

- if an error occurs in a mounting operation => the promise is rejected
(which will log the error)
- if an error occurs after the mounting operation and is not handled by
any error handlers => the error will be logged (with console.error).
Also, in that case, this commit adds a warning to explain that owl
destroys the root component, which will help developers understanding
what happened.
2022-02-11 10:46:44 +01:00
Géry Debongnie fd295b3be3 [FIX] compiler: properly handle <t> tags in some cases
The problem was that the compiler is based on the assumption that the
multi block received by the parser only occurs in some cases
where the structure of the template require a multi block, and it does
not work when we have random multiblock elsewhere.

We could fix the issue by modifying the code generator code to support
these usecases, or by simply removing these cases in the parser. Since
this seems more efficient, this is the approach taken by this commit.

Note that it was a good opportunity to simplify the parser.
2022-02-11 10:46:44 +01:00
Géry Debongnie e675f7ff5b [DOC] update changelog 2022-02-11 10:46:44 +01:00
Géry Debongnie 702fb3b253 [DOC] reorganize doc, unskip test, fix some links 2022-02-11 10:46:44 +01:00
Géry Debongnie 63fbcf99fd [FIX] blockdom: ignore attributes with undefined value 2022-02-11 10:46:44 +01:00
Géry Debongnie 14a6289f60 [IMP] component: add .bind suffix to props for easy binding 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 894deed13b [FIX] component: render in delayed willUpdateProps
Have a child component on which a render is triggered.
This component delays its willUpdateProps and makes a rendering during the willUpdateProps

Before this commit, renderings of the child were inconsistent across
its parent's renderings.

After this commit, it works as expected.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 6f435c36d8 [FIX] app: factorize to allow smoother developments in projects
It should be usefull to allow developpers to implement mounting/unmounting
if they wish to.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 5dddf8f9a3 [FIX] blockdom: do not propagate svg namespace to siblings 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 463eb4bb86 [FIX] hooks: useSubEnv supports arbitrary descriptors in env
Before this commit, when defining a getter in the env passed to useSubEnv,
the value was read, losing the definition of the property.

After this commit, declaring a getter in the env works as expected:
the property stays a getter.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) b66d5231d3 [FIX] app: support for arbitrary descriptors in env
Before this commit, when defining a getter in the env passed to the App,
the value was read, losing the definition of the property.

After this commit, declaring a getter in the env works as expected:
the property stays a getter.
2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) 3c12519277 [FIX] parser: correctly parse pre node within a div with new lines 2022-02-11 10:46:44 +01:00
Lucas Perais (lpe) c1a973a4d8 [FIX] component: error handling in class inheritance
Before this commit, class inheritance when using the onError hook was unclear nay wrong.

After this commit, error handlers are called from the bottom up  in the inheritance hierarchy.
If a handler doesn't rethrow the error, the handling stops there and no other handler is called.
If a handler does rethrow, the handlers declared in a parent class are executed.
2022-02-11 10:46:44 +01:00
Géry Debongnie e91e50a812 [REF] compiler: simplify all compiled templates 2022-02-11 10:46:44 +01:00
Géry Debongnie 2e176f135d [FIX] compiler: allow t-if with empty content 2022-02-11 10:46:44 +01:00
Géry Debongnie 49c7585998 [REF] components: unskip concurrency test 2022-02-11 10:46:44 +01:00
Géry Debongnie f32b1deb2c [FIX] move error handling out of fiber, fix complicated mounted issues 2022-02-11 10:46:44 +01:00
Géry Debongnie 1da3ecdbee [FIX] component: improve error handling
In the following situation: A parent of B, B parent of C, with an error
when C is mounted, caught by B and retriggering a rendering in B, then
the onMounted hook of A wasn't properly called. This commit fixes this
problem.
2022-02-11 10:46:44 +01:00
Géry Debongnie a1c619f094 [FIX] blockdom: properly handle references
Before this commit, there were situations where the reference numbers
were not properly set, which caused the blocks generated to crash
because the algorithm could not get correct references.
2022-02-11 10:46:44 +01:00
Géry Debongnie eceb3e6280 [IMP] component: render does not return a promise anymore 2022-02-11 10:46:44 +01:00
Géry Debongnie 8a1ac13975 [IMP] add support for top level comments 2022-02-11 10:46:44 +01:00
Géry Debongnie 779003e715 [REF] component: remove onDestroyed, implement onWillDestroy 2022-02-11 10:46:44 +01:00
Géry Debongnie 8c600fa539 [TESTS] test lifecycle in reactivity tests 2022-02-11 10:46:44 +01:00
Géry Debongnie bcc4fe2a27 [REF] tests: improve useLogLifecycle and helpers 2022-02-11 10:46:44 +01:00
Samuel Degueldre bb4948f3dc [FIX] reactivity: only call clearReactivesForCallback once on unmount 2022-02-11 10:46:44 +01:00
Samuel Degueldre cc4480e001 [FIX] reactivity: fix memory leak 2022-02-11 10:46:44 +01:00
Géry Debongnie 7143c2e39b [FIX] compiler: readd template name in compiled code 2022-02-11 10:46:44 +01:00
Géry Debongnie a8d8310b8e [REF] code_generator: move generating code to CodeTarget
Before this commit, we had two places with code that generate a function
code. Now, all this code is moved in a method 'generateCode' on
CodeTarget.
2022-02-11 10:46:44 +01:00
Géry Debongnie 0bbea351a6 [FIX] compiler: call dynamic templates with correct this 2022-02-11 10:46:44 +01:00
Géry Debongnie 2601a176c4 [FIX] slots: properly bind this in t-on arrow functions 2022-02-11 10:46:44 +01:00
Géry Debongnie 05a57d6da5 [REF] component: small cleanup
This commit makes it simpler to understand the way fibers are assigned
to nodes.
2022-02-11 10:46:44 +01:00
Géry Debongnie e6e6c31632 [FIX] component: concurrency issue
When a parent and a child were rendered at the same time, it was
possible for the 2 renders to decrement the same fiber internal
counter, which meant that the render was stalled.
2022-02-11 10:46:44 +01:00
Samuel Degueldre 3f66d9fe6c [FIX] slots: allow t-call and components in slot default content 2022-02-11 10:46:44 +01:00
Bruno Boi 5d8141a67c [IMP] owl: upgrade rollup-plugin-typescript2 to version 0.31.1 2022-02-11 10:46:44 +01:00
Samuel Degueldre 6459d8d289 [IMP] parser: normalize document before parsing 2022-02-11 10:46:44 +01:00
Samuel Degueldre 2943ca3921 [IMP] components: add test for template string in props 2022-02-11 10:46:44 +01:00
Samuel Degueldre 4866ed8e8a [IMP] parser: throw when using unsupported directive on component 2022-02-11 10:46:44 +01:00
Samuel Degueldre 93b88cad8d [FIX] components: allow prop names that are not valid bare property name 2022-02-11 10:46:44 +01:00
Mathieu Duckerts-Antoine b90180a9e0 [FIX] props: prop names can contain - 2022-02-11 10:46:44 +01:00
Bruno Boi 8239a5d2cd [DOC] Update CHANGELOG.md 2022-02-11 10:46:44 +01:00
Mathieu Duckerts-Antoine a073568667 [IMP] slots: via prop 'slots'
The slot inner working has been reworked. A prop "slots" is now passed
explicitely to the component. It looks like

{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }

with the objects slotInfo_i with mandatory keys "__render", "__ctx",
and optional key "__scope" and possibly others.

Here is how a slotInfo object can be created:
A slotInfo object is normally created by setting in a template something
like

<div>
    <t t-set-slot="foo" t-set-scope="scope" param_1="var" param_2="3">
        content
        <t t-esc="scope.bool"/>
        <t t-esc="scope.num"/>
    </t>
</div>

and it will be used somewhere like

<div>
    <t t-esc="props.slots.foo.param_1"/>
    <t t-slot="foo" bool="other_var" num="5">
</div>

In the above example, the function "__render" produces the block dom
element for the content of the t-set-slot.
The context "__ctx" will have a key "scope" with value { bool: ..., num: 5 }
and "__scope" will be set to "scope".
2022-02-11 10:41:18 +01:00
Samuel Degueldre 7143dd3ff5 [FIX] components: capture context in prop expressions 2022-02-11 10:41:18 +01:00
Samuel Degueldre c0cf2c9e3d [FIX] components: throw on duplicate t-key instead of hanging the app 2022-02-11 10:41:18 +01:00
Géry Debongnie db9658c140 [IMP] app: add templates in app config
Also, improve the parsing code
2022-02-11 10:41:18 +01:00
Géry Debongnie eb2c41aa91 [REM] tools: remove benchmarks/debug script
They are either no longer relevant, or less useful than some
alternatives (such as the js framework benchmark project)
2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) a45ca98dac [FIX] package: bump owl version to 2.0.0-alpha1 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) ced777f0be [FIX] tools: adapt playground to owl 2 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) 7df0a4e93f [FIX] index, reactivity: export reactive function in index 2022-02-11 10:41:18 +01:00
Samuel Degueldre f3555cfae0 [IMP] misc: update typescript to 4.5.2 2022-02-11 10:41:18 +01:00
Géry Debongnie 629b379ea9 [IMP] components: rename onRender->onWillRender, add onRendered 2022-02-11 10:41:18 +01:00
Samuel Degueldre a400fc5e69 [IMP] reactivity: overhaul reactivity system
This commit makes the reactivity system more fine grained and makes it
more eager to stop observing keys or objects when they are modified,
this results in fewer "false positive" notifications.
2022-02-11 10:41:18 +01:00
Bruno Boi 5d4a38ad0f [IMP] svg namespace support 2022-02-11 10:41:18 +01:00
Géry Debongnie 093218a067 [FIX] remove cyclic dependency, improve error typing (#982) 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) ed3e6dcbb6 [FIX] component, fiber: error_handling at the Fiber level
Before this commit, errors triggered at the level of the fiber (as opposed to at the level
of a component's rendering), were handled as the very top level of the rendering, that is,
in the scheduler.
This was wrong because components below in the rendering tree would not have a chance to handle their
children's or their own errors.

After this commit, error triggered in willPatch, onMounted and onPatched are correctly handled
at the closest component to where they were thrown.
2022-02-11 10:41:18 +01:00
Géry Debongnie cb107cef7d [REM] remove some outdated tests 2022-02-11 10:41:18 +01:00
Géry Debongnie aecc320c29 [MOV] move memo and portal to root folder 2022-02-11 10:41:18 +01:00
Géry Debongnie e580ec00fe [REM] tests: remove async root tests 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) ee5f6c7569 [FIX] component: error_handling on current component
Have a Child Compnent which has one component that succeeds and another
one that fails at its instanciation.
The Child component handles the Errors by rendering itself.

Before this commit, the error handling algorithm made impossible for the scheduler to finish.
This was because the current fiber was still counted as ongoing, when it was actually completed.

After this commit, this use case is handled correctly.
2022-02-11 10:41:18 +01:00
Géry Debongnie c627b0add8 [DOC] add a change log 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) b902edc1be [IMP] app, compiler: introduce t-out
t-out automatically escaped content when it is a string not marked
with the `markup` function

t-out renders the raw content if it is a Block, or if it has been marked
with the `markup` funtion.

t-esc has been kept since it is safe and is optimized to render text nodes.

all t-raw calls are in fact the same as t-out.
2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) 1761af9c24 [FIX][BREAKING] t-esc on component is not supported anymore 2022-02-11 10:41:18 +01:00
Bruno Boi db93ef08ff [IMP] templates: can load multiple at once and also from XMLDocument 2022-02-11 10:41:18 +01:00
Mathieu Duckerts-Antoine 03787cfb39 [REF] reactivity: new API 2022-02-11 10:41:18 +01:00
Mathieu Duckerts-Antoine 10745c52d0 [REF] tests: remove debugger 2022-02-11 10:41:18 +01:00
Mathieu Duckerts-Antoine 2b90e3a688 [FIX] reactivity: memory leak
The deletion of a key in an observed object did clear the observers of
that key but did not clear the atoms created (if any) when the key existed
(e.g. on the key value if it was trackable). This can lead to a growing
set of atoms that are useless but kept in memory if a lot of keys are
added/deleted. The same thing can happen if a key value is changed many
times and the values are trackable.

Here we fix the problem by
- keeping tracks of the objects that have been observed by an external call
  to the method "atom" (we call them seeds).
- remove all observer atoms that are not seeds for observers of at least
  one key deletion or key value change.

Note the fix is in some sense partial: a user could use the "atom" primitive
making the new system uncapable of avoiding a leak (see test "atom on an
object with a getter 3" where a getter is used in a weird way in an
observed object).
2022-02-11 10:41:18 +01:00
Bruno Boi b2ea241270 [IMP] hooks: reintroduce useExternalListener 2022-02-11 10:41:18 +01:00
Bruno Boi 81e5b24f2f [IMP] hooks: introduce useEffect
Co-Authored-By: Samuel Degueldre <sad@odoo.com>
2022-02-11 10:41:18 +01:00
Bruno Boi 717fd3b6ab [IMP] owl: mount util now takes an AppConfig 2022-02-11 10:41:18 +01:00
Bruno Boi 3fa1bb62f6 [IMP] env: env is now frozen, useSubEnv does not affect user env 2022-02-11 10:41:18 +01:00
Géry Debongnie 201f06c187 [FIX] tests: remove useless log 2022-02-11 10:41:18 +01:00
Géry Debongnie ae30d9db7d [FIX] portal: unskip some tests 2022-02-11 10:41:18 +01:00
Géry Debongnie af80cefa76 [FIX] component: fix lifecycle order 2022-02-11 10:41:18 +01:00
Géry Debongnie 1f6e84d141 [FIX] unskip tests 2022-02-11 10:41:18 +01:00
Géry Debongnie 1658d15b87 [REF] move event_bus into utils, readd 2 functions 2022-02-11 10:41:18 +01:00
Géry Debongnie c1439814bf [REM] tests: remove debug script tests 2022-02-11 10:41:18 +01:00
Géry Debongnie d5fbaff9f7 [REF] app: move TemplateSet into its own file 2022-02-11 10:41:18 +01:00
Géry Debongnie 8c16790471 [REF] move app and compiler code around 2022-02-11 10:41:18 +01:00
Géry Debongnie 9b8c582b32 [REF] component: fix typescript error 2022-02-11 10:41:18 +01:00
Géry Debongnie 1700a6fba3 [REF] compiler: rename qweb/ into compiler/ 2022-02-11 10:41:18 +01:00
Géry Debongnie 7513b1e507 [IMP] reactivity: slightly improve code and typing 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) e0c0306acd [IMP] component: re-introduce error handling in lifecycle 2022-02-11 10:41:18 +01:00
Samuel Degueldre bca6afeb90 [IMP] qweb: turn handlers into function expressions only
For the sake of consistency with vanilla JS, and to allow some things
that were previously not possible.
2022-02-11 10:41:18 +01:00
Mathieu Duckerts-Antoine 756d32daa0 [IMP] reactivity: Context replacement
Aim to replace the abstraction "Context" from OWL 1 with the new primitives
"atom" and "useState":

- notification is done only after a batch of modifications.
- observers are notified at most once for a batch.
- an observer of type component is notified (and rerendered)
  only if it does not have an ancestor that has to be notified for the
  same batch of operations (anywhere in the web of references!).
- notification of components is done on all levels "simultaneously".

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
2022-02-11 10:41:18 +01:00
Géry Debongnie 8169f05edc [IMP] pin prettier version to ensure consistent results 2022-02-11 10:41:18 +01:00
Samuel Degueldre 153f4379f4 [FIX] qweb: fix crash with ref an component in same slot 2022-02-11 10:41:18 +01:00
Samuel Degueldre 7ffb9afbd9 [FIX] qweb: fix crash when component only renders empty slot 2022-02-11 10:41:18 +01:00
Samuel Degueldre c03042b44d [IMP] qweb/components: remove t-ref on components
Refs to component expose a lot of implementation details that should be
private to parents. Parent to child communication should go through
props.
2022-02-11 10:41:18 +01:00
Bruno Boi df2d6b6a0e [IMP] qweb: reintroduce t-tag directive
will not be compatible with t-model directive !
2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) 7c04cc425e [IMP] qweb: compiler: support t-key on node and component without t-foreach 2022-02-11 10:41:18 +01:00
Bruno Boi 16f1e2c237 [IMP] bdom: support multiple synthetic events on one node 2022-02-11 10:41:18 +01:00
Bruno Boi 8a84b5be56 [IMP] qweb/attributes: uncomment two tests
- textarea with t-att-value
- select with t-att-value
2022-02-11 10:41:18 +01:00
Bruno Boi 219923d752 [IMP] qweb: introduce t-model directive
supported modifiers: lazy, trim, number
2022-02-11 10:41:18 +01:00
Bruno Boi 348b505e5f [IMP] tags: reintroduce inline css tag
The CSS tag is useful to define a css stylesheet in the javascript file:
```js
class MyComponent extends Component {
  static template = xml`
        <div class="my-component">some template</div>
    `;
  static style = css`
    .my-component {
      color: red;
    }
  `;
}
```

The `css` tag registers internally the css information. Then, whenever the first instance of the component is created, will add a <style> tag to the document <head>.

Original commit in Owl v1: 953778dc5
2022-02-11 10:41:18 +01:00
Mathieu Duckerts-Antoine d3745e4e5f [IMP] reactivity: new primitives for reactivity
fine grained reactivity:

existing key in source changes --> only observer having read the key are notified

add/delete key in source changes --> all source observers are notified

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
2022-02-11 10:41:18 +01:00
Mathieu Duckerts-Antoine 80cb6b7a91 [REF] Code prettification 2022-02-11 10:41:18 +01:00
Lucas Perais (lpe) 4cceb239dd [IMP] qweb, blockdom, components: t-on with modifiers
supported modifiers: capture, prevent, stop, self.
2022-02-11 10:20:09 +01:00
Lucas Perais (lpe) 2ae0149adb [IMP] blockdom: t-on supports synthetic and native event handler
Synthetic handler is a sort of event delegation that allows placing
only one listener on the document to improve performance. It is an opt-in option.

Native listener places the listener on the node itself.
2022-02-11 10:20:09 +01:00
Lucas Perais (lpe) 3eb63452e7 [FIX] qweb, component: remove support for t-on on component node 2022-02-11 10:20:09 +01:00
Lucas Perais (lpe) 1296964ae2 [FIX] qweb: t-key in t-foreach is mandatory, throws otherwise 2022-02-11 10:20:09 +01:00
Bruno Boi c71db28bc6 [FIX] qweb: reintroduce test on t-debug 2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine a0b2551e4a [IMP] tests: component mounting
We re-add some tests for component mounting.
2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine ebd2e4324f [IMP] app: mount app in "first-child" position
We reintroduce the possibility to mount the app in first position in
a target. The option "self" has been dropped since it is now possible
for a component to have several top level nodes.
2022-02-11 10:20:09 +01:00
Bruno Boi aa3148eddf [IMP] package.json: add watch arg to test:debug command
Before this commit
The command "npm run test:debug" runs the tests once.

After this commit
Jest runs in watch mode
2022-02-11 10:20:09 +01:00
Bruno Boi 42811344da [IMP] package.json: add remote test:debug command
Usage:
Open chrome://inspect then run in console:
> npm run test:debug ./path/to/your/testfile.ts
2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine a0e1af83ac [IMP] component: defaultProps application
We re-add the application of defaultProps. Note that the application
is done twice in dev mode.
2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine c16d8ed6de [IMP] component,qweb: props validation
We re-add the possibility to validate props when dev mode is active.
No change in the API right Now. The dev mode is activated via the
configure method of App class.
2022-02-11 10:20:09 +01:00
Samuel Degueldre 15e4c856da [FIX] component: correctly create a new node when previous is destroyed
Previously, when a component node had been created and destroyed, and
the corresponding component was then recreated, the destroyed node was
reused. This commit fixes that
2022-02-11 10:20:09 +01:00
Samuel Degueldre ced5d0f69f [IMP] *: re-add a bunch of tests 2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine b6eb4d009e [IMP] qweb: t-props directive
We reimplement the directive "t-props" and add some tests for it.
2022-02-11 10:20:09 +01:00
Géry Debongnie 8c71d99e5f [MOV] move lifecycle_hooks into component/ 2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine d7f3f4defe [FIX] qweb: re-add test on memory leak
Re-add test from 6e185f9.
2022-02-11 10:20:09 +01:00
Mathieu Duckerts-Antoine 0f2192604c [FIX] qweb: t-set directive
This commit reintroduces some tests for the t-set directive and make
them pass. For that, it was necessary to adapt the qweb compiler in
order to get the following behaviors:

A t-set can affect parent contexts (up to the first parent tagged as
boundary) when the key changed is found in one of the parent contexts.
Some context are marked as boundaries in such a way that

   - rendering contexts (e.g. components) cannot be modified via a t-set.
   - a t-set in a t-call body or in a called template can never change a
     context above the t-call context.

Code prettification has been done.
Snapshots have been modified.
2022-02-11 10:19:44 +01:00
Lucas Perais (lpe) 10df0b5f4a [FIX] re-introduce tests 2022-02-11 10:19:44 +01:00
Mathieu Duckerts-Antoine d569ea1c28 [IMP] qweb: throw error when t-component is not used with a 't' tag 2022-02-11 10:19:44 +01:00
Lucas Perais (lpe) 52fa81c510 [FIX] re-introduce some missing tests
The point is to have visibility on the development of the owl2 features.
This commit reintroduces some tests keeping them skipped in order to fulfill that purpose.

There still are some missing tests though.
2022-02-11 10:18:03 +01:00
Mathieu Duckerts-Antoine d6668e3439 [IMP] qweb: re-add support of t-on directive
We add some test for the t-on directive.

For making them pass, it was necessary to change the code produced by
compileTForeach: the const declaration is not done by using generateId
and there was some conflict with the variable names produced in
captureExpression. Consequently, many snapshots had to be changed.

Code prettification has been done too.
2022-02-11 10:18:03 +01:00
Mathieu Duckerts-Antoine ee1ef20ce1 [ADD] components: re-add a test for t-foreach directive 2022-02-11 10:18:03 +01:00
Samuel Degueldre 9d5ffe11c7 [IMP] components: re-add a bunch of components tests from owl 1
Some tests are skipped because they rely on not-yet-implemented
features.
2022-02-11 10:18:03 +01:00
Samuel Degueldre 900a3ee501 [IMP] components: add back style_class tests
The tests have been adapted to the new way of doing things, some tests
are skipped because they rely on features that are not implemented yet
2022-02-11 10:18:03 +01:00
Mathieu Duckerts-Antoine 0a544bd7e8 [ADD] Translation feature
This commit brings back the possibility to translate text nodes and
the attributes "label", "title", "placeholder", and "alt" in an app
configured with a suitable translation function.

It is also possible to deactivate translations under a node via
the directive t-translation="off".

For flexibility it is possible to define the list of translatable
attributes in the app.
2022-02-11 10:18:03 +01:00
Samuel Degueldre 4415cc8932 [REF] doc: remove references to router and store
Owl 2 will not have a router or store implemented inside the library
2022-02-11 10:18:03 +01:00
Samuel Degueldre efa147fdab [REF] qweb: use native Node and Element instead of custom Dom types 2022-02-11 10:18:03 +01:00
Géry Debongnie e746574a1d [REF] initial prototype of owl 2 2022-02-11 10:18:01 +01:00
Géry Debongnie c06049076a [FIX] qweb: renderToString should not escape twice text content
Since commit
https://github.com/odoo/owl/commit/b2f12a111524f37348b142ac248813f9cb25ca2e,
Owl escape text content twice. It seems that it was done to prevent
security issues, but without realizing that the standard t-esc method
already escapes.

closes #708
2021-12-14 15:21:37 +01:00
Samuel Degueldre bc04f727ac [REL] v1.4.10
#v1.4.10

- fix: make arrow function capture backwards-compatible
2021-12-07 15:32:05 +01:00
Samuel Degueldre 0bc9573a8a [FIX] component: make arrow-function capture backwards compatible
When fixing the absence of capture for arrow functions passed as props,
we unintentionally introduced a breaking change: bare function calls in
the arrow functions used to be called  with the rendering context as
their this value and this was no longer the case.

This commit fixes that by intentionally not capturing the value of
functions that are called withing the arrow function.
2021-12-07 15:27:00 +01:00
Samuel Degueldre 73f94fba3f [REL] v1.4.9
# v1.4.9

- fix: correctly capture the scope of arrow functions passed as props
2021-12-07 10:09:12 +01:00
Samuel Degueldre 7a16449724 [IMP] CI: make formatting check mandatory for ci check 2021-12-03 14:08:05 +01:00
Samuel Degueldre 718c765e3b [FIX] qweb: correctly capture the scope of arrow functions in props 2021-12-03 14:08:05 +01:00
Samuel Degueldre 150d620b8e [REF] run prettier 2021-12-03 14:08:05 +01:00
Géry Debongnie 307b936d01 [REL] v1.4.8
# v1.4.8

- fix: prevent crash in some rare cases
2021-11-03 13:44:46 +01:00
Achraf (abz) 6950f8e628 [FIX] components/fiber: Call patch only if target is valid
Currently in some cases, adding an attachment via lognote creates a traceback.
Error : shouldPatch is true while `vnode` is not defined, so `patch()` failed
This is a hotfix correcting this problem by calling `patch()` only if `shouldPatch` is true **and** the `vnode` is set.

opw-2645203
2021-11-03 11:37:04 +01:00
180 changed files with 20535 additions and 13232 deletions
+2 -1
View File
@@ -5,7 +5,7 @@ name: Node.js CI
on: on:
pull_request: pull_request:
branches: [ master, owl-next ] branches: [ master ]
jobs: jobs:
build: build:
@@ -25,3 +25,4 @@ jobs:
- run: npm install - run: npm install
- run: npm run test - run: npm run test
- run: npm run check-formatting - run: npm run check-formatting
- run: npm run build
+271 -57
View File
@@ -1,4 +1,4 @@
# ChangeLog # Changelog
This document contains an overview of all changes between Owl 1.x and This document contains an overview of all changes between Owl 1.x and
Owl 2.x, with some pointers on how to update the code. Owl 2.x, with some pointers on how to update the code.
@@ -8,13 +8,16 @@ patching the `setup` method of `Component` to auto register all the lifecycle
methods as hooks). This will be done for the transition period, but will be methods as hooks). This will be done for the transition period, but will be
removed after. removed after.
## Changes ## From Owl 1.x to Owl 2.0
All changes are documented here in no particular order.
**Components** **Components**
- components can now have empty content or multiple root nodes (htmlelement or text) ([details](#31-components-can-now-have-arbitrary-content)) - components can now have empty content or multiple root nodes (htmlelement or text) ([details](#31-components-can-now-have-arbitrary-content))
- new `useEffect` hook - breaking: component.el is removed ([details](#9-componentel-is-removed))
- new `onDestroyed`, `onWillRender` and `onRendered` hooks - new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
- new `onWillDestroy`, `onWillRender` and `onRendered` hooks ([doc](doc/reference/component.md#lifecycle))
- breaking: lifecycle methods are removed ([details](#1-component-lifecycle-methods-are-removed)) - breaking: lifecycle methods are removed ([details](#1-component-lifecycle-methods-are-removed))
- breaking: can no longer be mounted on detached DOM ([details](#2-components-can-no-longer-be-mounted-in-a-detached-dom-element)) - breaking: can no longer be mounted on detached DOM ([details](#2-components-can-no-longer-be-mounted-in-a-detached-dom-element))
- breaking: standalone `mount` method API is simpler ([details](#4-mount-method-api-is-simpler)) - breaking: standalone `mount` method API is simpler ([details](#4-mount-method-api-is-simpler))
@@ -22,46 +25,78 @@ removed after.
- breaking: components can no longer be unmounted/remounted ([details](#6-components-can-no-longer-be-unmountedremounted)) - breaking: components can no longer be unmounted/remounted ([details](#6-components-can-no-longer-be-unmountedremounted))
- breaking: template name is no longer inferred from the class name ([details](#7-template-name-is-no-longer-inferred-from-the-class-name)) - breaking: template name is no longer inferred from the class name ([details](#7-template-name-is-no-longer-inferred-from-the-class-name))
- breaking: components no longer have a `shouldUpdate` method ([details](#8-components-no-longer-have-a-shouldupdate-method)) - breaking: components no longer have a `shouldUpdate` method ([details](#8-components-no-longer-have-a-shouldupdate-method))
- breaking: component.el may be a text node, and is no longer `null` ([details](#9-componentel-may-be-a-text-node-and-is-no-longer-null))
- breaking: style/class on components are now regular props ([details](#10-styleclass-on-components-are-now-regular-props))
- breaking: components can no longer be mounted with position=self ([details](#11-components-can-no-longer-be-mounted-with-positionself)) - breaking: components can no longer be mounted with position=self ([details](#11-components-can-no-longer-be-mounted-with-positionself))
- breaking: `render` method does not return a promise anymore ([details](#35-render-method-does-not-return-a-promise-anymore))
- breaking: `catchError` method is replaced by `onError` hook ([details](#36-catcherror-method-is-replaced-by-onerror-hook))
- breaking: Support for inline css (`css` tag and static `style`) has been removed ([details](#37-support-for-inline-css-css-tag-and-static-style-has-been-removed))
- new: prop validation system can now describe that additional props are allowed (with `*`) ([doc](doc/reference/props.md#props-validation))
- breaking: prop validation system does not allow default prop on a mandatory (not optional) prop ([doc](doc/reference/props.md#props-validation))
- breaking: rendering a component does not necessarily render child components ([details](#40-rendering-a-component-does-not-necessarily-render-child-components))
**Templates**
- breaking: `t-foreach` should always have a corresponding `t-key` ([details](#20-t-foreach-should-always-have-a-corresponding-t-key))
- breaking: `t-ref` does not work on components ([details](#29-t-ref-does-not-work-on-component))
- breaking: `t-raw` directive has been removed (replaced by `t-out`) ([details](#38-t-raw-directive-has-been-removed-replaced-by-t-out))
- new: add support for synthetic events ([doc](doc/reference/event_handling.md#synthetic-events))
- breaking: style/class on components are now regular props ([details](#10-styleclass-on-components-are-now-regular-props))
- new: components can use the `.bind` suffix to bind function props ([doc](doc/reference/props.md#binding-function-props))
- breaking: `t-on` does not accept expressions, only functions ([details](#30-t-on-does-not-accept-expressions-only-functions))
- breaking: `t-on` does not work on components any more ([details](#12-t-on-does-not-work-on-components-any-more)) - breaking: `t-on` does not work on components any more ([details](#12-t-on-does-not-work-on-components-any-more))
- new: an error is thrown if an handler defined in a `t-on-` directive is not a function (failed silently previously in some cases)
- breaking: `t-component` no longer accepts strings ([details](#17-t-component-no-longer-accepts-strings)) - breaking: `t-component` no longer accepts strings ([details](#17-t-component-no-longer-accepts-strings))
- new: the `this` variable in template expressions is now bound to the component
**Portal**
- portals can now have arbitrary content (no longer restricted to one single child) **Reactivity**
- breaking: does no longer transfer dom events ([details](#13-portal-does-no-longer-transfer-dom-events))
- breaking: does render as an empty text node instead of `<portal/>` ([details](#14-portal-does-render-as-an-empty-text-node-instead-of-portal)) - finer grained reactivity: owl 2 tracks change per key/component
- finer grained reactivity: sub components can reobserve state ([doc](doc/reference/reactivity.md))
- new: `reactive` function: create reactive state (without being linked to a component) ([doc](doc/reference/reactivity.md#reactive))
- new: `markRaw` function: mark an object or array so that it is ignored by the reactivity system ([doc](doc/reference/reactivity.md#markraw))
- new: `toRaw` function: given a reactive objet, return the raw (non reactive) underlying object ([doc](doc/reference/reactivity.md#toraw))
**Slots** **Slots**
- breaking: `t-set` does not define a slot any more ([details](#3-t-set-will-no-longer-work-to-define-a-slot)) - breaking: `t-set` does not define a slot any more ([details](#3-t-set-will-no-longer-work-to-define-a-slot))
- slots capabilities have been improved ([doc](doc/reference/slots.md))
- params can be give to slot content (to pass information from slot owner to slot user)
- slots are given as a `prop` (and can be manipulated/propagated to sub components )
- slots can define scopes (to pass information from slot user to slot owner)
**Portal**
- Portal are now defined with `t-portal` ([details](#33-portal-are-now-defined-with-t-portal))
- portals can now have arbitrary content (no longer restricted to one single child)
- breaking: does no longer transfer dom events ([details](#13-portal-does-no-longer-transfer-dom-events))
- breaking: does render as an empty text node instead of `<portal/>` ([details](#14-portal-does-render-as-an-empty-text-node-instead-of-portal))
**Miscellaneous** **Miscellaneous**
- improved performance - improved performance
- much simpler code - much simpler code
- finer grained reactivity: owl 2 tracks change per key/component - new App class to encapsulate a root Owl component (with the config for that application) ([doc](doc/reference/app.md))
- finer grained reactivity: sub components can reobserve state - new `useEffect` hook ([doc](doc/reference/hooks.md#useeffect))
- new App class to encapsulate a root Owl component (with the config for that application)
- new `Memo` component
- breaking: `Context` is removed ([details](#15-context-is-removed)) - breaking: `Context` is removed ([details](#15-context-is-removed))
- breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty)) - breaking: `env` is now totally empty ([details](#16-env-is-now-totally-empty))
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen))
- new hook: `useChildSubEnv` (only applies to child components) ([details](#27-usechildsubenv-only-applies-to-child-components))
- breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level)) - breaking: most exports are exported at top level ([details](#18-most-exports-are-exported-at-top-level))
- breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes)) - breaking: properties are no longer set as attributes ([details](#19-properties-are-no-longer-set-as-attributes))
- breaking: `t-foreach` should always have a corresponding `t-key` ([details](#20-t-foreach-should-always-have-a-corresponding-t-key))
- breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget)) - breaking: `EventBus` api changed: it is now an `EventTarget` ([details](#21-eventbus-api-changed-it-is-now-an-eventtarget))
- breaking: `Store` is removed ([details](#22-store-is-removed)) - breaking: `Store` is removed ([details](#22-store-is-removed))
- breaking: `Router` is removed ([details](#23-router-is-removed)) - breaking: `Router` is removed ([details](#23-router-is-removed))
- breaking: transition system is removed ([details](#24-transition-system-is-removed)) - breaking: transition system is removed ([details](#24-transition-system-is-removed))
- breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates)) - breaking: no more global components or templates ([details](#25-no-more-global-components-or-templates))
- breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed)) - breaking: `AsyncRoot` utility component is removed ([details](#26-asyncroot-utility-component-is-removed))
- breaking: `useSubEnv` only applies to child components ([details](#27-usesubenv-only-applies-to-child-components)) - breaking: `renderToString` function on qweb has been removed ([details](#32-rendertostring-on-qweb-has-been-removed))
- breaking: `env` is now frozen ([details](#28-env-is-now-frozen)) - breaking: `debounce` utility function has been removed ([details](#34-debounce-utility-function-has-been-removed))
- breaking: `t-ref` does not work on components ([details](#29-t-ref-does-not-work-on-component)) - breaking: `browser` object has been removed ([details](#39-browser-object-has-been-removed))
- breaking: `t-on` does not accept expressions, only functions ([details](#30-t-on-does-not-accept-expressions-only-functions))
## Details/Rationale/Migration ## Details/Rationale/Migration
@@ -95,6 +130,8 @@ class MyComponent extends Component {
} }
``` ```
Documentation: [Component Lifecycle](doc/reference/component.md#lifecycle)
### 2. components can no longer be mounted in a detached dom element ### 2. components can no longer be mounted in a detached dom element
Nor document fragment. Nor document fragment.
@@ -146,6 +183,8 @@ because real applications will need to configure the templates, the translations
and other stuff. All complex usecases need to go through the new `App` class, and other stuff. All complex usecases need to go through the new `App` class,
that encapsulates the root of an owl application. that encapsulates the root of an owl application.
Documentation: [Mounting a component](doc/reference/app.md#mount-helper)
### 5. components can no longer be instantiated and mounted by hand ### 5. components can no longer be instantiated and mounted by hand
In Owl 1, it was possible to instantiate a component by hand: In Owl 1, it was possible to instantiate a component by hand:
@@ -209,40 +248,21 @@ Rationale: `shouldUpdate` is a dangerous method to use, that may cause a lot of
issues. Vue does not have such a mechanism (see https://github.com/vuejs/vue/issues/4255), issues. Vue does not have such a mechanism (see https://github.com/vuejs/vue/issues/4255),
because the reactivity system in Vue is smart enough to only rerender the minimal because the reactivity system in Vue is smart enough to only rerender the minimal
subset of components that is subscribed to a piece of state. Now, Owl 2 features subset of components that is subscribed to a piece of state. Now, Owl 2 features
a much more powerful reactivity system. a much more powerful reactivity system, so the same rationale applies: in a way,
it's like each Owl 2 component has a `shouldUpdate` method that precisely tracks
every value used by the component.
Migration code: remove the `shouldUpdate` methods. Then, maybe the following Migration code: remove the `shouldUpdate` methods, and it should work as well
ideas may help: as before.
- try to organize the state/architecture to minimize the number of state updates ### 9. component.el is removed
- take advantage of the finer reactivity system. For example, if we have a list
of items, with a component for each item, we can write this:
```js This comes from the fact that Owl 2 supports fragments (arbitrary content).
class Item extends Component {
setup() {
this.item = useState(this.props.item); // and only use this, not props.item
}
}
```
Doing so will make it that each `Item` component will register itself as an
observer of its own item, and will be the only component being rerendered when
its item object is updated.
- use the `Memo` component to wrap some piece of template. `Memo` memoize its
content, and only update itself if its props are different (shallow comparison):
```xml Migration: if one need a reference to the root htmlelement of a template, it is
<Memo a="state.a" b="state.b"> suggested to simply add a `ref` on it, and access the reference as needed.
<t t-esc="state.a"/>
<t t-esc="state.b"/>
<t t-esc="state.c"/>
</Memo>
```
### 9. component.el may be a text node, and is no longer `null` Documentation: [Refs](doc/reference/refs.md)
This comes from the fact that Owl 2 supports fragments (arbitrary content). When
it is not defined, it was `null` in Owl 1 and is `undefined` in owl 2.
### 10. style/class on components are now regular props ### 10. style/class on components are now regular props
@@ -285,7 +305,13 @@ compatible with the fact that a component can have a root `<div>` then later,
change it to something else, or even a text node. change it to something else, or even a text node.
Migration: no real way to do the same. Owl application needs to be appended or Migration: no real way to do the same. Owl application needs to be appended or
prepended in something, maybe a `div`. prepended in something, maybe a `div`. Remember that you the root component
can have multiple roots
Documentation:
- [Fragments](doc/reference/templates.md#fragments)
- [Mounting a component](doc/reference/app.md#mount-helper)
### 12. `t-on` does not work on components any more ### 12. `t-on` does not work on components any more
@@ -312,8 +338,16 @@ the component API to accept explicitely a callback as props.
```xml ```xml
<SomeComponent onSomeEvent="doSomething"/> <SomeComponent onSomeEvent="doSomething"/>
<!-- or alternatively: -->
<SomeComponent onSomeEvent.bind="doSomething"/>
``` ```
Note that one of the example above uses the `.bind` suffix, to bind the function
prop to the component. Most of the time, binding the function is necessary, and
using the `.bind` suffix is very helpful in that case.
Documentation: [Binding function props](doc/reference/props.md#binding-function-props)
### 13. Portal does no longer transfer DOM events ### 13. Portal does no longer transfer DOM events
In Owl 1, a Portal component would listen to events emitted on its portalled In Owl 1, a Portal component would listen to events emitted on its portalled
@@ -371,6 +405,8 @@ Migration: there is no proper way to get an equivalent. The closest is to get
a reference to the root App using `this.__owl__.app`. If you need to do this, a reference to the root App using `this.__owl__.app`. If you need to do this,
let us know. If this is a legitimate usecase, we may add a `useApp` hook. let us know. If this is a legitimate usecase, we may add a `useApp` hook.
Documentation: [Environment](doc/reference/environment.md)
### 17. `t-component` no longer accepts strings ### 17. `t-component` no longer accepts strings
In owl 1, we could write this: In owl 1, we could write this:
@@ -396,6 +432,8 @@ the implementation is slightly simpler.
Migration: simply using `constructor.components.Coucou` instead of `Coucou` will Migration: simply using `constructor.components.Coucou` instead of `Coucou` will
do the trick. do the trick.
Documentation: [Component](doc/reference/component.md#dynamic-sub-components)
### 18. most exports are exported at top level ### 18. most exports are exported at top level
Most exports are flattened: for ex, `onMounted` is in owl, not in `owl.hooks`. Most exports are flattened: for ex, `onMounted` is in owl, not in `owl.hooks`.
@@ -452,6 +490,8 @@ rewritten like this: `bus.addEventListener("event-type", (({detail: info}) => {.
Do not forget to similarly replace `bus.off(...)` by `bus.removeEventListener(...)` Do not forget to similarly replace `bus.off(...)` by `bus.removeEventListener(...)`
Documentation: [EventBus](doc/reference/utils.md#eventbus)
### 22. `Store` is removed ### 22. `Store` is removed
The Store system had been abandoned in owl 2. The Store system had been abandoned in owl 2.
@@ -513,14 +553,12 @@ either a fallback when the data is not ready, or the actual component with data
as props. If there is no escape, and `AsyncRoot` is needed, please reach out to as props. If there is no escape, and `AsyncRoot` is needed, please reach out to
us so we can study this usecase. us so we can study this usecase.
### 27. `useSubEnv` only applies to child components ### 27. `useChildSubEnv` (only applies to child components)
In Owl 1, a call to `useSubEnv` would define a new environment for the children
AND the component. It now only defines an environment for the children.
Rationale: This was a subtle cause for bugs: some code had to be rrun
before the call to `useSubEnv`, otherwise it could interfere with the sub environment.
In Owl, a call to `useSubEnv` would define a new environment for the children
AND the component. It is very useful, but in some cases, one only need to update
the children component environment. This can now be done with a new hook:
[`useChildSubEnv`](doc/reference/hooks.md#usesubenv-and-usechildsubenv)
### 28. `env` is now frozen ### 28. `env` is now frozen
@@ -535,6 +573,8 @@ components. This use case still works with `useSubEnv`.
Migration: use `useSubEnv` instead of writing directly to the env. Also, note Migration: use `useSubEnv` instead of writing directly to the env. Also, note
that the environment given to the App can initially contain anything. that the environment given to the App can initially contain anything.
Documentation: [Environment](doc/reference/environment.md)
### 29. `t-ref` does not work on component ### 29. `t-ref` does not work on component
Before, `t-ref` could be used to get a reference to a child component. It no Before, `t-ref` could be used to get a reference to a child component. It no
@@ -575,6 +615,8 @@ adapted like this:
<button t-on-click="() => this.someFunction(someVar)">blabla</button> <button t-on-click="() => this.someFunction(someVar)">blabla</button>
``` ```
Documentation: [Event Handling](doc/reference/event_handling.md)
### 31. components can now have arbitrary content ### 31. components can now have arbitrary content
Before Owl 2, components had to limit themselves to one single htmlelement as Before Owl 2, components had to limit themselves to one single htmlelement as
@@ -586,3 +628,175 @@ So, the following template works for components:
<div>2</div> <div>2</div>
hello hello
``` ```
Documentation: [Fragments](doc/reference/templates.md#fragments)
### 32. `renderToString` on QWeb has been removed
Rationale: the `renderToString` function was a qweb method, which made sense because
the qweb instance knew all templates. But now, the closest analogy is the `App`
class, but it is not as convenient, since the `app` instance is no longer visible
to components (while before, `qweb` was in the environment).
Also, this can easily be done in userspace, by mounting a component in a div. For example:
```js
export async function renderToString(template, context) {
class C extends Component {
static template = template;
setup () {
Object.assign(this, context);
}
}
const div = document.createElement('div');
document.body.appendChild(div);
const app = new App(C);
await app.mount(div);
const result = div.innerHTML;
app.destroy();
div.remove();
return result;
}
```
The function above works for most cases, but is asynchronous. An alternative
function could look like this:
```js
const { App, blockDom } = owl;
const app = new App(Component); // act as a template repository
function renderToString(template, context = {}) {
app.addTemplate(template, template, { allowDuplicate: true });
const templateFn = app.getTemplate(template);
const bdom = templateFn(context, {});
const div = document.createElement('div')
blockDom.mount(bdom, div);
return div.innerHTML;
}
```
This is a synchronous function, so it will not work with components, but it should
be useful for most simple templates.
Also note that these two examples do not translate their templates. To do that,
they need to be modified to pass the proper translate function to the `App`
configuration.
### 33. Portal are now defined with `t-portal`
Before Owl 2, one could use the `Portal` component by importing it and using it.
Now, it is no longer available. Instead, we can simply use the `t-portal` directive:
```xml
<div>
some content
<span t-portal="'body'">
portalled content
</span>
<div>
```
Rationale: it makes it slightly simpler to use (just need the directive, instead
of having to import and use a sub component), it makes the implementation slightly
simpler as well. Also, it prevents subclassing the Portal component, which could
be dangerous, since it is really doing weird stuff under the hood, and could
easily be broken inadvertendly.
### 34. `debounce` utility function has been removed
Rationale: it did not really help that much, is available as utility function
elsewhere, so, we decided to have a smaller footprint by focusing Owl on what
it does best.
### 35. `render` method does not return a promise anymore
Rationale: using the `render` method directly and waiting for it to complete
was slightly un-declarative. Also, it can be done using the lifecycle hooks
any way.
Migration: if necessary, one can use the lifecycle hooks to execute code after
the next mounted/patched operation.
### 36. `catchError` method is replaced by `onError` hook
The `catchError` method was used to provide a way to components to handle errors
occurring during the component lifecycle. This has been replaced by a `onError`
hook, with a similar API.
Rationale: `catchError` felt a little big awkward, when most of the way we
interact with componentss is via hooks. Using hooks felt more natural and
consistent.
Migration: mostly replace all `catchError` methods by `onError` hooks in the
`setup` method.
Documentation: [Error Handling](doc/reference/error_handling.md)
## 37. Support for inline css (`css` tag and static `style`) has been removed
Rationale: Owl tries to focus on what it does best, and supporting inline css
was not a priority. It used to support some simplified scss language, but it
was feared that it would cause more trouble than it was worth. Also, it seems
like it can be done in userspace.
Migration: it seems possible to implement an equivalent solution using hooks. A
simple implementation could look like this:
```js
let cache = {};
function useStyle(css) {
if (!css in cache) {
const sheet = document.createElement("style");
sheet.innerHTML = css;
cache[css] = sheet;
document.head.appendChild(sheet);
}
}
```
## 38. `t-raw` directive has been removed (replaced by `t-out`)
To match the Odoo qweb server implementation, Owl does no longer implement `t-raw`.
It is replaced by the `t-out` directive, which is safer: it requires the data
to be marked explicitely as markup if it is to be inserted without escaping.
Otherwise, it will be escaped (just like `t-esc`).
Migration: replace all `t-raw` uses by `t-out`, and uses the `markup` function
to mark all the js values.
Documentation: [Outputting data](doc/reference/templates.md#outputting-data)
## 39. `browser` object has been removed
Rationale: the `browser` object caused more trouble than it was worth. Also, it
seems like this should be done in user space, not at the framework level.
Migration: code should just be adapted to either use another browser object,
or to use native browser function (and then, just mock them directly).
## 40. Rendering a component does not necessarily render child components
Before, if one had the following component tree:
```mermaid
graph TD;
A-->B;
A-->C;
```
when `A` would render, it would also render `B` and `C`. Now, in Owl 2, it will
(shallow) compare the before and after props, and `B` or `C` will only be rerendered
if their props have changed.
Now, the question is what happens if the props have changed, but in a deeper way?
In that case, Owl will know, because each props are now reactive. So, if some
inner value read by `B` was changed, then only `B` will be updated.
Rationale: This was just not possible in Owl 1, but it now possible. This is
due to the rewriteof the underlying rendering engine and the reactivity
system. The goal is to have a big performance boost in large screen with many
components: now Owl only rerender what is strictly useful.
+62 -66
View File
@@ -1,4 +1,4 @@
<h1 align="center">🦉 <a href="https://odoo.github.io/owl/">OWL Framework</a> 🦉</h1> <h1 align="center">🦉 <a href="https://odoo.github.io/owl/">Owl Framework</a> 🦉</h1>
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![npm version](https://badge.fury.io/js/@odoo%2Fowl.svg)](https://badge.fury.io/js/@odoo%2Fowl) [![npm version](https://badge.fury.io/js/@odoo%2Fowl.svg)](https://badge.fury.io/js/@odoo%2Fowl)
@@ -6,112 +6,111 @@
_Class based components with hooks, reactive state and concurrent mode_ _Class based components with hooks, reactive state and concurrent mode_
**Try it online!** you can experiment with the Owl framework in an online [playground](https://odoo.github.io/owl/playground).
## Project Overview ## Project Overview
The Odoo Web Library (OWL) is a smallish (~<20kb gzipped) UI framework intended to The Odoo Web Library (Owl) is a smallish (~<20kb gzipped) UI framework built by
be the basis for the [Odoo](https://www.odoo.com/) Web Client. Owl is a modern [Odoo](https://www.odoo.com/) for its products. Owl is a modern
framework, written in Typescript, taking the best ideas from React and Vue in a framework, written in Typescript, taking the best ideas from React and Vue in a
simple and consistent way. Owl's main features are: simple and consistent way. Owl's main features are:
- a declarative component system, - a declarative component system,
- a reactivity system based on hooks, - a fine grained reactivity system similar to Vue,
- concurrent mode by default, - hooks
- fragments
- asynchronous rendering
Owl components are defined with ES6 classes, they use QWeb templates, an Owl components are defined with ES6 classes and xml templates, uses an
underlying virtual DOM, integrates beautifully with hooks, and the rendering is underlying virtual DOM, integrates beautifully with hooks, and the rendering is
asynchronous. asynchronous.
**Try it online!** An online playground is available at Quick links:
[https://odoo.github.io/owl/playground](https://odoo.github.io/owl/playground)
to let you experiment with the Owl framework. There are some code examples to
showcase some interesting features.
Owl is currently stable. Possible future changes are explained in the - [documentation](#documentation),
[roadmap](roadmap.md). - [changelog](CHANGELOG.md) (from Owl 1.x to 2.x),
- [playground](https://odoo.github.io/owl/playground)
## Why Owl?
Why did Odoo decide to make Yet Another Framework? This is really a question
that deserves [a long answer](doc/miscellaneous/why_owl.md). But in short, we believe that
while the current state of the art frameworks are excellent, they are not
optimized for our use case, and there is still room for something else.
If you are interested in a comparison with React or Vue, you will
find some more additional information [here](doc/miscellaneous/comparison.md).
## Example ## Example
Here is a short example to illustrate interactive components: Here is a short example to illustrate interactive components:
```javascript ```javascript
const { Component, useState, mount } = owl; const { Component, useState, mount, xml } = owl;
const { xml } = owl.tags;
class Counter extends Component { class Counter extends Component {
static template = xml` static template = xml`
<button t-on-click="state.value++"> <button t-on-click="() => state.value = state.value + props.increment">
Click Me! [<t t-esc="state.value"/>] Click Me! [<t t-esc="state.value"/>]
</button>`; </button>`;
state = useState({ value: 0 }); state = useState({ value: 0 });
} }
class App extends Component { class Root extends Component {
static template = xml` static template = xml`
<div> <span>Hello Owl</span>
<span>Hello Owl</span> <Counter increment="2"/>`;
<Counter />
</div>`;
static components = { Counter }; static components = { Counter };
} }
mount(App, { target: document.body }); mount(Root, document.body);
``` ```
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate). Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
Also, all examples here uses the [`xml` helper](doc/reference/tags.md#xml-tag) to define inline templates. Also, all examples here uses the [`xml` helper](doc/reference/templates.md#inline-templates) to define inline templates.
But this is not mandatory, many applications will load templates separately. But this is not mandatory, many applications will load templates separately.
More interesting examples can be found on the More interesting examples can be found on the
[playground](https://odoo.github.io/owl/playground) application. [playground](https://odoo.github.io/owl/playground) application.
## Design Principles
OWL is designed to be used in highly dynamic applications where changing
requirements are common and code needs to be maintained by large teams.
- **XML based**: templates are based on the XML format, which allows interesting
applications. For example, they could be stored in a database and modified
dynamically with `xpaths`.
- **templates compilation in the browser**: this may not be a good fit for all
applications, but if you need to generate dynamically user interfaces in the
browser, this is very powerful. For example, a generic form view component
could generate a specific form user interface for each various models, from a XML view.
- **no toolchain required**: this is extremely useful for some applications, if,
for various reasons (security/deployment/dynamic modules/specific assets tools),
it is not ok to use standard web tools based on `npm`.
Owl is not designed to be fast nor small (even though it is quite good on those
two topics). It is a no nonsense framework to build applications. There is only
one way to define components (with classes). There is no black magic. It just
works.
## Documentation ## Documentation
A complete documentation for Owl can be found here: ### Learning Owl
- [Main documentation page](doc/readme.md). Are you new to Owl? This is the place to start!
Some of the most important pages are: - [Tutorial: create a TodoList application](doc/learning/tutorial_todoapp.md)
- [Tutorial: TodoList application](doc/learning/tutorial_todoapp.md)
- [How to start an Owl project](doc/learning/quick_start.md) - [How to start an Owl project](doc/learning/quick_start.md)
- [QWeb templating language](doc/reference/qweb_templating_language.md) - [How to test Components](doc/learning/how_to_test.md)
- [Component](doc/reference/component.md)
- [Hooks](doc/reference/hooks.md)
### Reference
- [Overview](doc/readme.md)
- [App](doc/reference/app.md)
- [Component](doc/reference/component.md)
- [Component Lifecycle](doc/reference/component.md#lifecycle)
- [Concurrency Model](doc/reference/concurrency_model.md)
- [Dynamic sub components](doc/reference/component.md#dynamic-sub-components)
- [Environment](doc/reference/environment.md)
- [Error Handling](doc/reference/error_handling.md)
- [Event Handling](doc/reference/event_handling.md)
- [Form Input Bindings](doc/reference/input_bindings.md)
- [Fragments](doc/reference/templates.md#fragments)
- [Hooks](doc/reference/hooks.md)
- [Loading Templates](doc/reference/app.md#loading-templates)
- [Mounting a component](doc/reference/app.md#mount-helper)
- [Portal](doc/reference/portal.md)
- [Props](doc/reference/props.md)
- [Props Validation](doc/reference/props.md#props-validation)
- [Reactivity](doc/reference/reactivity.md)
- [Rendering SVG](doc/reference/templates.md#rendering-svg)
- [Refs](doc/reference/refs.md)
- [Slots](doc/reference/slots.md)
- [Sub components](doc/reference/component.md#sub-components)
- [Sub templates](doc/reference/templates.md#sub-templates)
- [Templates (Qweb)](doc/reference/templates.md)
- [Translations](doc/reference/translations.md)
- [Utils](doc/reference/utils.md)
### Other Topics
- [Notes On Owl Architecture](doc/miscellaneous/architecture.md)
- [Comparison with React/Vue](doc/miscellaneous/comparison.md)
- [Why did Odoo build Owl?](doc/miscellaneous/why_owl.md)
- [Changelog (from owl 1.x to 2.x)](CHANGELOG.md)
- [Notes on compiled templates](doc/miscellaneous/compiled_template.md)
## Installing Owl ## Installing Owl
@@ -123,8 +122,5 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here: If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.4.7](https://github.com/odoo/owl/releases/tag/v1.4.7) - [owl-1.4.10](https://github.com/odoo/owl/releases/tag/v1.4.10)
## License
OWL is [LGPL licensed](./LICENSE).
+11 -46
View File
@@ -30,27 +30,21 @@ To help with this, it is useful to have a `helper.js` file that contains some
common utility functions: common utility functions:
```js ```js
let lastFixture = null;
export function makeTestFixture() { export function makeTestFixture() {
let fixture = document.createElement("div"); let fixture = document.createElement("div");
document.body.appendChild(fixture); document.body.appendChild(fixture);
if (lastFixture) {
lastFixture.remove();
}
lastFixture = fixture;
return fixture; return fixture;
} }
export function nextTick() { export async function nextTick() {
let requestAnimationFrame = owl.Component.scheduler.requestAnimationFrame; await new Promise((resolve) => setTimeout(resolve));
return new Promise(function(resolve) { await new Promise((resolve) => requestAnimationFrame(resolve));
setTimeout(() => requestAnimationFrame(() => resolve()));
});
}
export function makeTestEnv() {
// application specific. It needs a way to load actual templates
const templates = ...;
return {
qweb: new QWeb(templates),
..., // each service can be mocked here
};
} }
``` ```
@@ -59,7 +53,7 @@ With such a file, a typical test suite for Jest will look like this:
```js ```js
// in SomeComponent.test.js // in SomeComponent.test.js
import { SomeComponent } from "../../src/ui/SomeComponent"; import { SomeComponent } from "../../src/ui/SomeComponent";
import { nextTick, makeTestFixture, makeTestEnv} from '../helpers'; import { nextTick, makeTestFixture } from '../helpers';
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -70,9 +64,6 @@ let env: Env;
beforeEach(() => { beforeEach(() => {
fixture = makeTestFixture(); fixture = makeTestFixture();
env = makeTestEnv();
// we set here the default environment for each component created in the test
Component.env = env;
}); });
afterEach(() => { afterEach(() => {
@@ -85,7 +76,7 @@ afterEach(() => {
describe("SomeComponent", () => { describe("SomeComponent", () => {
test("component behaves as expected", async () => { test("component behaves as expected", async () => {
const props = {...}; // depends on the component const props = {...}; // depends on the component
const comp = await mount(SomeComponent, { target: fixture, props }); const comp = await mount(SomeComponent, fixture, { props });
// do some assertions // do some assertions
expect(...).toBe(...); expect(...).toBe(...);
@@ -102,29 +93,3 @@ describe("SomeComponent", () => {
Note that Owl does wait for the next animation frame to actually update the DOM. Note that Owl does wait for the next animation frame to actually update the DOM.
This is why it is necessary to wait with the `nextTick` (or other methods) to This is why it is necessary to wait with the `nextTick` (or other methods) to
make sure that the DOM is up-to-date. make sure that the DOM is up-to-date.
It is sometimes useful to wait until Owl is completely done updating components
(in particular, if we have a highly concurrent user interface). This next
helper simply polls every 20ms the internal Owl task queue and returns a promise
which resolves when it is empty:
```js
function afterUpdates() {
return new Promise((resolve, reject) => {
let timer = setTimeout(poll, 20);
let counter = 0;
function poll() {
counter++;
if (owl.Component.scheduler.tasks.length) {
if (counter > 10) {
reject(new Error("timeout"));
} else {
timer = setTimeout(poll);
}
} else {
resolve();
}
}
});
}
```
-52
View File
@@ -1,52 +0,0 @@
# 🦉 How to write Single File Components 🦉
It is very useful to group code by feature instead of by type of file. It makes
it easier to scale application to larger size.
To do so, Owl has two small helpers that make it easy to define a
template or a stylesheet inside a javascript (or typescript) file: the
[`xml`](../reference/tags.md#xml-tag) and [`css`](../reference/tags.md#css-tag)
helper.
This means that the template, the style and the javascript code can be defined in
the same file. For example:
```js
const { Component } = owl;
const { xml, css } = owl.tags;
// -----------------------------------------------------------------------------
// TEMPLATE
// -----------------------------------------------------------------------------
const TEMPLATE = xml/* xml */ `
<div class="main">
<Sidebar/>
<Content />
</div>`;
// -----------------------------------------------------------------------------
// STYLE
// -----------------------------------------------------------------------------
const STYLE = css/* css */ `
.main {
display: grid;
grid-template-columns: 200px auto;
}
`;
// -----------------------------------------------------------------------------
// CODE
// -----------------------------------------------------------------------------
class Main extends Component {
static template = TEMPLATE;
static style = STYLE;
static components = { Sidebar, Content };
// rest of component...
}
```
Note that the above example has an inline xml comment, just after the `xml` call.
This is useful for some editor plugins, such as the VS Code addon
`Comment tagged template`, which, if installed, add syntax highlighting to the
content of the template string.
-133
View File
@@ -1,133 +0,0 @@
# 🦉 Quick Overview 🦉
Owl components in an application are used to define a (dynamic) tree of components.
```
Root
/ \
A B
/ \
C D
```
**State:** each component can manage its own local state. It is a simple ES6
class, there are no special rules:
```js
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = { value: 0 };
increment() {
this.state.value++;
this.render();
}
}
```
The example above shows a component with a local state. Note that since there
is nothing magical to the `state` object, we need to manually call the `render`
function whenever we update it. This can quickly become annoying (and not
efficient if we do it too much). There is a better way: using the `useState`
hook, which transforms an object into a reactive version of itself:
```js
const { useState } = owl.hooks;
class Counter extends Component {
static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = useState({ value: 0 });
increment() {
this.state.value++;
}
}
```
Note that the `t-on-click` handler can even be replaced by an inline statement:
```xml
<button t-on-click="state.value++">
```
**Props:** sub components often needs some information from their parents. This
is done by adding the required information to the template. This will then be
accessible by the sub component in the `props` object. Note that there is an
important rule here: the information contained in the `props` object is not
owned by the sub component, and should never be modified.
```js
class Child extends Component {
static template = xml`<div>Hello <t t-esc="props.name"/></div>`;
}
class Parent extends Component {
static template = xml`
<div>
<Child name="'Owl'" />
<Child name="'Framework'" />
</div>`;
static components = { Child };
}
```
**Communication:** there are multiple ways to communicate information between
components. However, the two most important ways are the following:
- from parent to children: by using `props`,
- from a children to one of its parent: by triggering events.
The following example illustrate both mechanisms:
```js
class OrderLine extends Component {
static template = xml`
<div t-on-click="add">
<div><t t-esc="props.line.name"/></div>
<div>Quantity: <t t-esc="props.line.quantity"/></div>
</div>`;
add() {
this.trigger("add-to-order", { line: this.props.line });
}
}
class Parent extends Component {
static template = xml`
<div t-on-add-to-order="addToOrder">
<OrderLine
t-foreach="orders"
t-as="line"
line="line" />
</div>`;
static components = { OrderLine };
orders = useState([
{ id: 1, name: "Coffee", quantity: 0 },
{ id: 2, name: "Tea", quantity: 0 },
]);
addToOrder(event) {
const line = event.detail.line;
line.quantity++;
}
}
```
In this example, the `OrderLine` component trigger a `add-to-order` event. This
will generate a DOM event which will bubble along the DOM tree. It will then be
intercepted by the parent component, which will then get the line (from the
`detail` key) and then increment its quantity. See the page on [event handling](../reference/event_handling.md)
for more details on how events work.
Note that this example would have also worked if the `OrderLine` component
directly modifies the `line` object. However, this is not a good practice: this
only works because the `props` object received by the child component is reactive,
so the child component is then coupled to the parents implementation.
+38 -47
View File
@@ -36,6 +36,8 @@ hello_owl/
The file `owl.js` can be downloaded from the last release published at The file `owl.js` can be downloaded from the last release published at
[https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). It [https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). It
is a single javascript file which export all Owl into the global `owl` object. is a single javascript file which export all Owl into the global `owl` object.
Note that there are multiple files, and in this case, we need one of the two
files suffixed with `.iife`: they are built to be directly used in a browser.
Now, `index.html` should contain the following: Now, `index.html` should contain the following:
@@ -45,30 +47,24 @@ Now, `index.html` should contain the following:
<head> <head>
<title>Hello Owl</title> <title>Hello Owl</title>
<script src="owl.js"></script> <script src="owl.js"></script>
<script src="app.js"></script>
</head> </head>
<body></body> <body>
<script src="app.js"></script>
</body>
</html> </html>
``` ```
And `app.js` should look like this: And `app.js` should look like this:
```js ```js
const { Component, mount } = owl; const { Component, mount, xml } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
// Owl Components // Owl Components
class App extends Component { class Root extends Component {
static template = xml`<div>Hello Owl</div>`; static template = xml`<div>Hello Owl</div>`;
} }
// Setup code mount(Root, document.body);
function setup() {
mount(App, target: { document.body })
}
whenReady(setup);
``` ```
Now, simply loading this html file in a browser should display a welcome message. Now, simply loading this html file in a browser should display a welcome message.
@@ -93,14 +89,16 @@ Let us start a new project with the following file structure:
``` ```
hello_owl/ hello_owl/
src/ src/
app.js
index.html index.html
main.js main.js
owl.js owl.js
root.js
``` ```
As previously, the file `owl.js` can be downloaded from the last release published at As previously, the file `owl.js` can be downloaded from the last release published at
[https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). [https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases).
Note that there are multiple files, and in this case, we need one of the two
files suffixed with `.iife`: they are built to be directly used in a browser.
Now, `index.html` should contain the following: Now, `index.html` should contain the following:
@@ -110,37 +108,33 @@ Now, `index.html` should contain the following:
<head> <head>
<title>Hello Owl</title> <title>Hello Owl</title>
<script src="owl.js"></script> <script src="owl.js"></script>
<script src="main.js" type="module"></script>
</head> </head>
<body></body> <body>
<script src="main.js" type="module"></script>
</body>
</html> </html>
``` ```
Not that the `main.js` script tag has the `type="module"` attribute. This means Not that the `main.js` script tag has the `type="module"` attribute. This means
that the browser will parse the script as a module, and load all its dependencies. that the browser will parse the script as a module, and load all its dependencies.
Here is the content of `app.js` and `main.js`: Here is the content of `root.js` and `main.js`:
```js ```js
// app.js ---------------------------------------------------------------------- // root.js ----------------------------------------------------------------------
const { Component, mount } = owl; const { Component, mount, xml } = owl;
const { xml } = owl.tags;
export class App extends Component { export class Root extends Component {
static template = xml`<div>Hello Owl</div>`; static template = xml`<div>Hello Owl</div>`;
} }
// main.js --------------------------------------------------------------------- // main.js ---------------------------------------------------------------------
import { App } from "./app.js"; import { Root } from "./root.js";
function setup() { mount(Root, document.body);
mount(App, { target: document.body });
}
owl.utils.whenReady(setup);
``` ```
The `main.js` file import the `app.js` file. Note that the import statement has The `main.js` file imports the `root.js` file. Note that the import statement has
a `.js` suffix, which is important. Most text editor can understand this syntax a `.js` suffix, which is important. Most text editor can understand this syntax
and will provide autocompletion. and will provide autocompletion.
@@ -193,11 +187,11 @@ hello_owl/
index.html index.html
src/ src/
components/ components/
App.js Root.js
main.js main.js
tests/ tests/
components/ components/
App.test.js Root.test.js
helpers.js helpers.js
.gitignore .gitignore
package.json package.json
@@ -224,13 +218,15 @@ Note that there are no `<script>` tag here. They will be injected by webpack.
Now, let's have a look at the javascript files: Now, let's have a look at the javascript files:
```js ```js
// src/components/App.js ------------------------------------------------------- // src/components/Root.js -------------------------------------------------------
import { Component, tags, useState } from "@odoo/owl"; import { Component, xml, useState } from "@odoo/owl";
const { xml } = tags; export class Root extends Component {
static template = xml`
<div t-on-click="update">
Hello <t t-esc="state.text"/>
</div>`;
export class App extends Component {
static template = xml`<div t-on-click="update">Hello <t t-esc="state.text"/></div>`;
state = useState({ text: "Owl" }); state = useState({ text: "Owl" });
update() { update() {
this.state.text = this.state.text === "Owl" ? "World" : "Owl"; this.state.text = this.state.text === "Owl" ? "World" : "Owl";
@@ -239,16 +235,12 @@ export class App extends Component {
// src/main.js ----------------------------------------------------------------- // src/main.js -----------------------------------------------------------------
import { utils, mount } from "@odoo/owl"; import { utils, mount } from "@odoo/owl";
import { App } from "./components/App"; import { Root } from "./components/Root";
function setup() { mount(Root, document.body);
mount(App, { target: document.body });
}
utils.whenReady(setup); // tests/components/Root.test.js ------------------------------------------------
import { Root } from "../../src/components/Root";
// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers"; import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl"; import { mount } from "@odoo/owl";
@@ -262,9 +254,9 @@ afterEach(() => {
fixture.remove(); fixture.remove();
}); });
describe("App", () => { describe("Root", () => {
test("Works as expected...", async () => { test("Works as expected...", async () => {
await mount(App, { target: fixture }); await mount(Root, fixture);
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>"); expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
click(fixture, "div"); click(fixture, "div");
@@ -278,9 +270,8 @@ import { Component } from "@odoo/owl";
import "regenerator-runtime/runtime"; import "regenerator-runtime/runtime";
export async function nextTick() { export async function nextTick() {
return new Promise(function (resolve) { await new Promise((resolve) => setTimeout(resolve));
setTimeout(() => Component.scheduler.requestAnimationFrame(() => resolve())); await new Promise((resolve) => requestAnimationFrame(resolve));
});
} }
export function makeTestFixture() { export function makeTestFixture() {
+297 -325
View File
@@ -50,10 +50,11 @@ the following content:
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>OWL Todo App</title> <title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="app.css" />
</head>
<body>
<script src="owl.js"></script> <script src="owl.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
</head> </body>
<body></body>
</html> </html>
``` ```
@@ -71,44 +72,34 @@ Note that we put everything inside an immediately executed function to avoid lea
anything to the global scope. anything to the global scope.
Finally, `owl.js` should be the last version downloaded from the Owl repository (you can use `owl.min.js` if you prefer). Be aware that you should download the `owl.iife.js` or `owl.iife.min.js`, because these files Finally, `owl.js` should be the last version downloaded from the Owl repository (you can use `owl.min.js` if you prefer). Be aware that you should download the `owl.iife.js` or `owl.iife.min.js`, because these files
are built to run directly on the browser (other files such as `owl.cjs.js` are are built to run directly on the browser, and rename it `owl.js` (other files such as `owl.cjs.js` are
built to be bundled by other tools). built to be bundled by other tools).
Now, the project should be ready. Loading the `index.html` file into a browser Now, the project should be ready. Loading the `index.html` file into a browser
should show an empty page, with the title `Owl Todo App`, and it should log a should show an empty page, with the title `Owl Todo App`, and it should log a
message such as `hello owl 1.0.0` in the console. message such as `hello owl 2.x.y` in the console.
## 2. Adding a first component ## 2. Adding a first component
An Owl application is made out of [components](../reference/component.md), with An Owl application is made out of [components](../reference/component.md), with
a single root component. Let us start by defining an `App` component. Replace the a single root component. Let us start by defining a `Root` component. Replace the
content of the function in `app.js` by the following code: content of the function in `app.js` by the following code:
```js ```js
const { Component, mount } = owl; const { Component, mount, xml } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
// Owl Components // Owl Components
class App extends Component { class Root extends Component {
static template = xml`<div>todo app</div>`; static template = xml`<div>todo app</div>`;
} }
// Setup code mount(Root, document.body);
function setup() {
mount(App, { target: document.body });
}
whenReady(setup);
``` ```
Now, reloading the page in a browser should display a message. Now, reloading the page in a browser should display a message.
The code is pretty simple, but let us explain the last line in more detail. The The code is pretty simple: we define a component with an inline template, then
browser tries to execute the javascript code in `app.js` as quickly as possible, mount it in the document body.
and it could happen that the DOM is not ready yet when we try to mount the `App`
component. To avoid this situation, we use the [`whenReady`](../reference/utils.md#whenready)
helper to delay the execution of the `setup` function until the DOM is ready.
Note 1: in a larger project, we would split the code in multiple files, with Note 1: in a larger project, we would split the code in multiple files, with
components in a sub folder, and a main file that would initialize the application. components in a sub folder, and a main file that would initialize the application.
@@ -125,7 +116,7 @@ class App extends Component {}
App.template = xml`<div>todo app</div>`; App.template = xml`<div>todo app</div>`;
``` ```
Note 3: writing inline templates with the [`xml` helper](../reference/tags.md#xml-tag) Note 3: writing inline templates with the [`xml` helper](../reference/templates.md#inline-templates)
is nice, but there is no syntax highlighting, and this makes it very easy to is nice, but there is no syntax highlighting, and this makes it very easy to
have malformed xml. Some editors support syntax highlighting for this situation. have malformed xml. Some editors support syntax highlighting for this situation.
For example, VS Code has an addon `Comment tagged template`, which, if installed, For example, VS Code has an addon `Comment tagged template`, which, if installed,
@@ -149,20 +140,20 @@ with the following keys:
tasks. Since the title is something created/edited by the user, it offers tasks. Since the title is something created/edited by the user, it offers
no guarantee that it is unique. So, we will generate a unique `id` number for no guarantee that it is unique. So, we will generate a unique `id` number for
each task. each task.
- `title`: a string, to explain what the task is about. - `text`: a string, to explain what the task is about.
- `isCompleted`: a boolean, to keep track of the status of the task - `isCompleted`: a boolean, to keep track of the status of the task
Now that we decided on the internal format of the state, let us add some demo Now that we decided on the internal format of the state, let us add some demo
data and a template to the `App` component: data and a template to the `App` component:
```js ```js
class App extends Component { class Root extends Component {
static template = xml/* xml */ ` static template = xml/* xml */ `
<div class="task-list"> <div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id"> <t t-foreach="tasks" t-as="task" t-key="task.id">
<div class="task"> <div class="task">
<input type="checkbox" t-att-checked="task.isCompleted"/> <input type="checkbox" t-att-checked="task.isCompleted"/>
<span><t t-esc="task.title"/></span> <span><t t-esc="task.text"/></span>
</div> </div>
</t> </t>
</div>`; </div>`;
@@ -170,26 +161,26 @@ class App extends Component {
tasks = [ tasks = [
{ {
id: 1, id: 1,
title: "buy milk", text: "buy milk",
isCompleted: true, isCompleted: true,
}, },
{ {
id: 2, id: 2,
title: "clean house", text: "clean house",
isCompleted: false, isCompleted: false,
}, },
]; ];
} }
``` ```
The template contains a [`t-foreach`](../reference/qweb_templating_language.md#loops) loop to iterate The template contains a [`t-foreach`](../reference/templates.md#loops) loop to iterate
through the tasks. It can find the `tasks` list from the component, since the through the tasks. It can find the `tasks` list from the component, since the
component is the rendering context. Note that we use the `id` of each task as a component is the rendering context. Note that we use the `id` of each task as a
`t-key`, which is very common. There are two css classes: `task-list` and `task`, `t-key`, which is very common. There are two css classes: `task-list` and `task`,
that we will use in the next section. that we will use in the next section.
Finally, notice the use of the `t-att-checked` attribute: Finally, notice the use of the `t-att-checked` attribute:
prefixing an attribute by [`t-att`](../reference/qweb_templating_language.md#dynamic-attributes) makes prefixing an attribute by [`t-att`](../reference/templates.md#dynamic-attributes) makes
it dynamic. Owl will evaluate the expression and set it as the value of the it dynamic. Owl will evaluate the expression and set it as the value of the
attribute. attribute.
@@ -245,29 +236,25 @@ a little bit:
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Task Component // Task Component
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const TASK_TEMPLATE = xml /* xml */`
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"/>
<span><t t-esc="props.task.title"/></span>
</div>`;
class Task extends Component { class Task extends Component {
static template = TASK_TEMPLATE; static template = xml /* xml */`
static props = ["task"]; <div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"/>
<span><t t-esc="props.task.title"/></span>
</div>`;
static props = ["task"];
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// App Component // Root Component
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const APP_TEMPLATE = xml /* xml */` class Root extends Component {
static template = xml /* xml */`
<div class="task-list"> <div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id"> <t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/> <Task task="task"/>
</t> </t>
</div>`; </div>`;
class App extends Component {
static template = APP_TEMPLATE;
static components = { Task }; static components = { Task };
tasks = [ tasks = [
@@ -276,14 +263,9 @@ class App extends Component {
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Setup code // Setup
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
function setup() { mount(Root, document.body, {dev: true});
owl.config.mode = "dev";
mount(App, { target: document.body });
}
whenReady(setup);
``` ```
A lot of stuff happened here: A lot of stuff happened here:
@@ -292,24 +274,22 @@ A lot of stuff happened here:
- whenever we define a sub component, it needs to be added to the static - whenever we define a sub component, it needs to be added to the static
[`components`](../reference/component.md#static-properties) [`components`](../reference/component.md#static-properties)
key of its parent, so Owl can get a reference to it, key of its parent, so Owl can get a reference to it,
- the templates have been extracted out of the components, to make it easier to
differentiate the "view/template" code from the "script/behavior" code,
- the `Task` component has a `props` key: this is only useful for validation - the `Task` component has a `props` key: this is only useful for validation
purpose. It says that each `Task` should be given exactly one prop, named purpose. It says that each `Task` should be given exactly one prop, named
`task`. If this is not the case, Owl will throw an `task`. If this is not the case, Owl will throw an
[error](../reference/props_validation.md). This is extremely [error](../reference/props.md#props-validation). This is extremely
useful when refactoring components useful when refactoring components
- finally, to activate the props validation, we need to set Owl's - finally, to activate the props validation, we need to set Owl's
[mode](../reference/config.md#mode) to `dev`. This is done in the `setup` [mode](../reference/app.md#configuration) to `dev`. This is done in the last argument
function. Note that this should be removed when an app is used in a real of the `mount` function. Note that this should be removed when an app is used in a real
production environment, since `dev` mode is slightly slower, due to extra production environment, since `dev` mode is slightly slower, due to extra
checks and validations. checks and validations.
## 6. Adding tasks (part 1) ## 6. Adding tasks (part 1)
We still use a list of hardcoded tasks. It's really time to give the user a way We still use a list of hardcoded tasks. It's really time to give the user a way
to add tasks himself. The first step is to add an input to the `App` component. to add tasks himself. The first step is to add an input to the `Root` component.
But this input will be outside of the task list, so we need to adapt `App` But this input will be outside of the task list, so we need to adapt `Root`
template, js, and css: template, js, and css:
```xml ```xml
@@ -327,9 +307,9 @@ template, js, and css:
addTask(ev) { addTask(ev) {
// 13 is keycode for ENTER // 13 is keycode for ENTER
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
const title = ev.target.value.trim(); const text = ev.target.value.trim();
ev.target.value = ""; ev.target.value = "";
console.log('adding task', title); console.log('adding task', text);
// todo // todo
} }
} }
@@ -358,10 +338,9 @@ task. Notice that when you load the page, the input is not focused. But adding
tasks is a core feature of a task list, so let us make it as fast as possible by tasks is a core feature of a task list, so let us make it as fast as possible by
focusing the input. focusing the input.
Since `App` is a component, it has a We need to execute code when the `Root` component is ready (mounted). Let's do
[`mounted` lifecycle method](../reference/component.md#lifecycle) that we can that using the `onMounted` hook. We will also need to get a reference to the
implement. We will also need to get a reference to the input, by using the input, by using the `t-ref` directive with the [`useRef`](../reference/hooks.md#useref) hook:
`t-ref` directive with the [`useRef`](../reference/hooks.md#useref) hook:
```xml ```xml
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/> <input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
@@ -369,22 +348,21 @@ implement. We will also need to get a reference to the input, by using the
```js ```js
// on top of file: // on top of file:
const { useRef } = owl.hooks; const { Component, mount, xml, useRef, onMounted } = owl;
``` ```
```js ```js
// in App // in App
inputRef = useRef("add-input"); setup() {
const inputRef = useRef("add-input");
mounted() { onMounted(() => inputRef.el.focus());
this.inputRef.el.focus();
} }
``` ```
The `inputRef` is defined as a class field, so it is equivalent to defining it This is a very common situation: whenever we need to perform some actions depending
in the constructor. It simply instructs Owl to keep a reference to anything with on the lifecycle of a component, we need to do it in the `setup` method, by using
the corresponding `t-ref` keyword. We then implement the `mounted` lifecycle one of the lifecycle hook. Here, we first get a reference to the `inputRef`,
method, where we now have an active reference that we can use to focus the input. then in the `onMounted` hook, we simply focus the html element.
## 7. Adding tasks (part 2) ## 7. Adding tasks (part 2)
@@ -405,12 +383,12 @@ Now, the `addTask` method can be implemented:
addTask(ev) { addTask(ev) {
// 13 is keycode for ENTER // 13 is keycode for ENTER
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
const title = ev.target.value.trim(); const text = ev.target.value.trim();
ev.target.value = ""; ev.target.value = "";
if (title) { if (text) {
const newTask = { const newTask = {
id: this.nextId++, id: this.nextId++,
title: title, text: text,
isCompleted: false, isCompleted: false,
}; };
this.tasks.push(newTask); this.tasks.push(newTask);
@@ -428,7 +406,7 @@ the user interface. We can fix the issue by making `tasks` reactive, with the
```js ```js
// on top of the file // on top of the file
const { useRef, useState } = owl.hooks; const { Component, mount, xml, useRef, onMounted, useState } = owl;
// replace the task definition in App with the following: // replace the task definition in App with the following:
tasks = useState([]); tasks = useState([]);
@@ -443,12 +421,8 @@ did not change in opacity. This is because there is no code to modify the
`isCompleted` flag. `isCompleted` flag.
Now, this is an interesting situation: the task is displayed by the `Task` Now, this is an interesting situation: the task is displayed by the `Task`
component, but it is not the owner of its state, so it cannot modify it. Instead, component, but it is not the owner of its state, so ideally, it should not modify it.
we want to communicate the request to toggle a task to the `App` component. However, for now, that's what we will do (this will be improved in a later step).
Since `App` is a parent of `Task`, we can
[trigger](../reference/event_handling.md) an event in `Task` and listen
for it in `App`.
In `Task`, change the `input` to: In `Task`, change the `input` to:
```xml ```xml
@@ -459,36 +433,23 @@ and add the `toggleTask` method:
```js ```js
toggleTask() { toggleTask() {
this.trigger('toggle-task', {id: this.props.task.id}); this.props.task.isCompleted = !this.props.task.isCompleted;
}
```
We now need to listen for that event in the `App` template:
```xml
<div class="task-list" t-on-toggle-task="toggleTask">
```
and implement the `toggleTask` code:
```js
toggleTask(ev) {
const task = this.tasks.find(t => t.id === ev.detail.id);
task.isCompleted = !task.isCompleted;
} }
``` ```
## 9. Deleting tasks ## 9. Deleting tasks
Let us now add the possibility do delete tasks. To do that, we first need to add Let us now add the possibility do delete tasks. This is different from the previous
a trash icon on each task, then we will proceed just like in the previous section. feature: deleting task has to be done on the task itself, but the actual operation
need to be done on the task list. So, we need to communicate the request to the
`Root` component. This is usually done by providing a callback in a prop.
First, let us update the `Task` template, css and js: First, let us update the `Task` template, css and js:
```xml ```xml
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''"> <div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted" t-on-click="toggleTask"/> <input type="checkbox" t-att-checked="props.task.isCompleted" t-on-click="toggleTask"/>
<span><t t-esc="props.task.title"/></span> <span><t t-esc="props.task.text"/></span>
<span class="delete" t-on-click="deleteTask">🗑</span> <span class="delete" t-on-click="deleteTask">🗑</span>
</div> </div>
``` ```
@@ -517,218 +478,226 @@ First, let us update the `Task` template, css and js:
``` ```
```js ```js
static props = ["task", "onDelete"];
deleteTask() { deleteTask() {
this.trigger('delete-task', {id: this.props.task.id}); this.props.onDelete(this.props.task);
} }
``` ```
And now, we need to listen to the `delete-task` event in `App`: And now, we need to provide the `onDelete` callback to each tasks in the `Root`
component:
```xml ```xml
<div class="task-list" t-on-toggle-task="toggleTask" t-on-delete-task="deleteTask"> <Task task="task" onDelete.bind="deleteTask"/>
``` ```
```js ```js
deleteTask(ev) { deleteTask(task) {
const index = this.tasks.findIndex(t => t.id === ev.detail.id); const index = this.tasks.findIndex(t => t.id === task.id);
this.tasks.splice(index, 1); this.tasks.splice(index, 1);
} }
``` ```
Notice that the `onDelete` prop is defined with a `.bind` suffix: this is a special
suffix that makes sure the function callback is bound to the component.
## 10. Using a store ## 10. Using a store
Looking at the code, it is apparent that we now have code to handle tasks Looking at the code, it is apparent that all the code handling tasks is scattered
scattered in more than one place. Also, it mixes UI code and business logic all around the application. Also, it mixes UI code and business logic
code. Owl has a way to manage state separately from the user interface: a code. Owl does not provide any high level abstraction to manage business logic,
[`Store`](../reference/store.md). but it is easy to do it with the basic reactivity primitives (`useState` and `reactive`).
Let us use it in our application. This is a pretty large refactoring (for our Let us use it in our application to implement a central store. This is a pretty
application), since it involves extracting all task related code out of the large refactoring (for our application), since it involves extracting all task
components. Here is the new content of the `app.js` file: related code out of the components. Here is the new content of the `app.js` file:
```js ```js
const { Component, Store, mount } = owl; const { Component, mount, xml, useRef, onMounted, useState, reactive, useEnv } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
const { useRef, useDispatch, useStore } = owl.hooks;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Store // Store
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const actions = { function useStore() {
addTask({ state }, title) { const env = useEnv();
title = title.trim(); return useState(env.store);
if (title) { }
// -------------------------------------------------------------------------
// TaskList
// -------------------------------------------------------------------------
class TaskList {
nextId = 1;
tasks = [];
addTask(text) {
text = text.trim();
if (text) {
const task = { const task = {
id: state.nextId++, id: this.nextId++,
title: title, text: text,
isCompleted: false, isCompleted: false,
}; };
state.tasks.push(task); this.tasks.push(task);
} }
}, }
toggleTask({ state }, id) {
const task = state.tasks.find((t) => t.id === id); toggleTask(task) {
task.isCompleted = !task.isCompleted; task.isCompleted = !task.isCompleted;
}, }
deleteTask({ state }, id) {
const index = state.tasks.findIndex((t) => t.id === id); deleteTask(task) {
state.tasks.splice(index, 1); const index = this.tasks.findIndex((t) => t.id === task.id);
}, this.tasks.splice(index, 1);
}; }
const initialState = { }
nextId: 1,
tasks: [], function createTaskStore() {
}; return reactive(new TaskList());
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Task Component // Task Component
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const TASK_TEMPLATE = xml/* xml */ ` class Task extends Component {
static template = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''"> <div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted" <input type="checkbox" t-att-checked="props.task.isCompleted" t-on-click="() => store.toggleTask(props.task)"/>
t-on-click="dispatch('toggleTask', props.task.id)"/> <span><t t-esc="props.task.text"/></span>
<span><t t-esc="props.task.title"/></span> <span class="delete" t-on-click="() => store.deleteTask(props.task)">🗑</span>
<span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑</span>
</div>`; </div>`;
class Task extends Component {
static template = TASK_TEMPLATE;
static props = ["task"]; static props = ["task"];
dispatch = useDispatch();
setup() {
this.store = useStore();
}
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// App Component // Root Component
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const APP_TEMPLATE = xml/* xml */ ` class Root extends Component {
static template = xml/* xml */ `
<div class="todo-app"> <div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/> <input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
<div class="task-list"> <div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id"> <t t-foreach="store.tasks" t-as="task" t-key="task.id">
<Task task="task"/> <Task task="task"/>
</t> </t>
</div> </div>
</div>`; </div>`;
class App extends Component {
static template = APP_TEMPLATE;
static components = { Task }; static components = { Task };
inputRef = useRef("add-input"); setup() {
tasks = useStore((state) => state.tasks); const inputRef = useRef("add-input");
dispatch = useDispatch(); onMounted(() => inputRef.el.focus());
this.store = useStore();
mounted() {
this.inputRef.el.focus();
} }
addTask(ev) { addTask(ev) {
// 13 is keycode for ENTER // 13 is keycode for ENTER
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
this.dispatch("addTask", ev.target.value); this.store.addTask(ev.target.value);
ev.target.value = ""; ev.target.value = "";
} }
} }
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Setup code // Setup
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
function setup() { const env = {
owl.config.mode = "dev"; store: createTaskStore(),
const store = new Store({ actions, state: initialState }); };
App.env.store = store; mount(Root, document.body, { dev: true, env });
mount(App, { target: document.body });
}
whenReady(setup);
``` ```
## 11-Saving tasks in local storage ## 11. Saving tasks in local storage
Now, our TodoApp works great, except if the user closes or refresh the browser! Now, our TodoApp works great, except if the user closes or refresh the browser!
It is really inconvenient to only keep the state of the application in memory. It is really inconvenient to only keep the state of the application in memory.
To fix this, we will save the tasks in the local storage. With our current To fix this, we will save the tasks in the local storage. With our current
codebase, it is a simple change: only the setup code needs to be updated. codebase, it is a simple change: we need to save tasks to local storage and
listen to any change.
```js ```js
function makeStore() { class TaskList {
const localState = window.localStorage.getItem("todoapp"); constructor(tasks) {
const state = localState ? JSON.parse(localState) : initialState; this.tasks = tasks || [];
const store = new Store({ state, actions }); const taskIds = this.tasks.map((t) => t.id);
store.on("update", null, () => { this.nextId = taskIds.length ? Math.max(...taskIds) + 1 : 1;
localStorage.setItem("todoapp", JSON.stringify(store.state)); }
}); // ...
return store;
} }
function setup() { function createTaskStore() {
owl.config.mode = "dev"; const saveTasks = () => localStorage.setItem("todoapp", JSON.stringify(taskStore.tasks));
const env = { store: makeStore() }; const initialTasks = JSON.parse(localStorage.getItem("todoapp") || "[]");
mount(App, { target: document.body, env }); const taskStore = reactive(new TaskList(initialTasks), saveTasks);
saveTasks();
return taskStore;
} }
``` ```
The key point is to use the fact that the store is an The key point is that the `reactive` function takes a callback that will be called
[`EventBus`](../reference/event_bus.md) which triggers an `update` event every time an observed value is changed. Note that we need to call the `saveTasks`
whenever it is updated. method initially to make sure we observe all current values.
## 12. Filtering tasks ## 12. Filtering tasks
We are almost done, we can add/update/delete tasks. The only missing feature is We are almost done, we can add/update/delete tasks. The only missing feature is
the possibility to display the task according to their completed status. We will the possibility to display the task according to their completed status. We will
need to keep track of the state of the filter in `App`, then filter the visible need to keep track of the state of the filter in `Root`, then filter the visible
tasks according to its value. tasks according to its value.
```js ```js
// on top of file, readd useState: class Root extends Component {
const { useRef, useDispatch, useState, useStore } = owl.hooks; static template = xml /* xml */`
<div class="todo-app">
// in App: <input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
filter = useState({value: "all"}) <div class="task-list">
get displayedTasks() {
switch (this.filter.value) {
case "active": return this.tasks.filter(t => !t.isCompleted);
case "completed": return this.tasks.filter(t => t.isCompleted);
case "all": return this.tasks;
}
}
setFilter(filter) {
this.filter.value = filter;
}
```
Finally, we need to display the visible filters. We can do that, and at the
same time, display the number of tasks in a small panel below the main list:
```xml
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
<div class="task-list">
<t t-foreach="displayedTasks" t-as="task" t-key="task.id"> <t t-foreach="displayedTasks" t-as="task" t-key="task.id">
<Task task="task"/> <Task task="task"/>
</t> </t>
</div> </div>
<div class="task-panel" t-if="tasks.length"> <div class="task-panel" t-if="store.tasks.length">
<div class="task-counter"> <div class="task-counter">
<t t-esc="displayedTasks.length"/> <t t-esc="displayedTasks.length"/>
<t t-if="displayedTasks.length lt tasks.length"> <t t-if="displayedTasks.length lt store.tasks.length">
/ <t t-esc="tasks.length"/> / <t t-esc="store.tasks.length"/>
</t> </t>
task(s) task(s)
</div> </div>
<div> <div>
<span t-foreach="['all', 'active', 'completed']" <span t-foreach="['all', 'active', 'completed']"
t-as="f" t-key="f" t-as="f" t-key="f"
t-att-class="{active: filter.value===f}" t-att-class="{active: filter.value===f}"
t-on-click="setFilter(f)" t-on-click="() => this.setFilter(f)"
t-esc="f"/> t-esc="f"/>
</div> </div>
</div> </div>
</div> </div>`;
setup() {
...
this.filter = useState({ value: "all" });
}
get displayedTasks() {
const tasks = this.store.tasks;
switch (this.filter.value) {
case "active": return tasks.filter(t => !t.isCompleted);
case "completed": return tasks.filter(t => t.isCompleted);
case "all": return tasks;
}
}
setFilter(filter) {
this.filter.value = filter;
}
}
``` ```
```css ```css
@@ -753,8 +722,8 @@ same time, display the number of tasks in a small panel below the main list:
} }
``` ```
Notice here that we set dynamically the class of the filter with the object Notice here that we set dynamically the css class of the filter with the object
syntax: each key is a class that we want to set if its value is truthy. syntax.
## 13. The Final Touch ## 13. The Final Touch
@@ -769,16 +738,16 @@ the user experience.
} }
``` ```
2. Make the title of a task clickable, to toggle its checkbox: 2. Make the text of a task clickable, to toggle its checkbox:
```xml ```xml
<input type="checkbox" t-att-checked="props.task.isCompleted" <input type="checkbox" t-att-checked="props.task.isCompleted"
t-att-id="props.task.id" t-att-id="props.task.id"
t-on-click="dispatch('toggleTask', props.task.id)"/> t-on-click="dispatch('toggleTask', props.task.id)"/>
<label t-att-for="props.task.id"><t t-esc="props.task.title"/></label> <label t-att-for="props.task.id"><t t-esc="props.task.text"/></label>
``` ```
3. Strike the title of completed task: 3. Strike the text of completed task:
```css ```css
.task.done label { .task.done label {
@@ -810,142 +779,145 @@ For reference, here is the final code:
```js ```js
(function () { (function () {
const { Component, Store, mount } = owl; const { Component, mount, xml, useRef, onMounted, useState, reactive, useEnv } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
const { useRef, useDispatch, useState, useStore } = owl.hooks;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Store // Store
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const actions = { function useStore() {
addTask({ state }, title) { const env = useEnv();
title = title.trim(); return useState(env.store);
if (title) { }
// -------------------------------------------------------------------------
// TaskList
// -------------------------------------------------------------------------
class TaskList {
constructor(tasks) {
this.tasks = tasks || [];
const taskIds = this.tasks.map((t) => t.id);
this.nextId = taskIds.length ? Math.max(...taskIds) + 1 : 1;
}
addTask(text) {
text = text.trim();
if (text) {
const task = { const task = {
id: state.nextId++, id: this.nextId++,
title: title, text: text,
isCompleted: false, isCompleted: false,
}; };
state.tasks.push(task); this.tasks.push(task);
} }
}, }
toggleTask({ state }, id) {
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
},
};
const initialState = { toggleTask(task) {
nextId: 1, task.isCompleted = !task.isCompleted;
tasks: [], }
};
deleteTask(task) {
const index = this.tasks.findIndex((t) => t.id === task.id);
this.tasks.splice(index, 1);
}
}
function createTaskStore() {
const saveTasks = () => localStorage.setItem("todoapp", JSON.stringify(taskStore.tasks));
const initialTasks = JSON.parse(localStorage.getItem("todoapp") || "[]");
const taskStore = reactive(new TaskList(initialTasks), saveTasks);
saveTasks();
return taskStore;
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Task Component // Task Component
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const TASK_TEMPLATE = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"
t-att-id="props.task.id"
t-on-click="dispatch('toggleTask', props.task.id)"/>
<label t-att-for="props.task.id"><t t-esc="props.task.title"/></label>
<span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑</span>
</div>`;
class Task extends Component { class Task extends Component {
static template = TASK_TEMPLATE; static template = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox"
t-att-id="props.task.id"
t-att-checked="props.task.isCompleted"
t-on-click="() => store.toggleTask(props.task)"/>
<label t-att-for="props.task.id"><t t-esc="props.task.text"/></label>
<span class="delete" t-on-click="() => store.deleteTask(props.task)">🗑</span>
</div>`;
static props = ["task"]; static props = ["task"];
dispatch = useDispatch();
setup() {
this.store = useStore();
}
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// App Component // Root Component
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
const APP_TEMPLATE = xml/* xml */ ` class Root extends Component {
<div class="todo-app"> static template = xml/* xml */ `
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/> <input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
<div class="task-list"> <div class="task-list">
<Task t-foreach="displayedTasks" t-as="task" t-key="task.id" task="task"/> <t t-foreach="displayedTasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div> </div>
<div class="task-panel" t-if="tasks.length"> <div class="task-panel" t-if="store.tasks.length">
<div class="task-counter"> <div class="task-counter">
<t t-esc="displayedTasks.length"/> <t t-esc="displayedTasks.length"/>
<t t-if="displayedTasks.length lt tasks.length"> <t t-if="displayedTasks.length lt store.tasks.length">
/ <t t-esc="tasks.length"/> / <t t-esc="store.tasks.length"/>
</t> </t>
task(s) task(s)
</div> </div>
<div> <div>
<span t-foreach="['all', 'active', 'completed']" <span t-foreach="['all', 'active', 'completed']"
t-as="f" t-key="f" t-as="f" t-key="f"
t-att-class="{active: filter.value===f}" t-att-class="{active: filter.value===f}"
t-on-click="setFilter(f)" t-on-click="() => this.setFilter(f)"
t-esc="f"/> t-esc="f"/>
</div> </div>
</div> </div>
</div>`; </div>`;
class App extends Component {
static template = APP_TEMPLATE;
static components = { Task }; static components = { Task };
inputRef = useRef("add-input"); setup() {
tasks = useStore((state) => state.tasks); const inputRef = useRef("add-input");
filter = useState({ value: "all" }); onMounted(() => inputRef.el.focus());
dispatch = useDispatch(); this.store = useStore();
this.filter = useState({ value: "all" });
mounted() {
this.inputRef.el.focus();
} }
addTask(ev) { addTask(ev) {
// 13 is keycode for ENTER // 13 is keycode for ENTER
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
this.dispatch("addTask", ev.target.value); this.store.addTask(ev.target.value);
ev.target.value = ""; ev.target.value = "";
} }
} }
get displayedTasks() { get displayedTasks() {
const tasks = this.store.tasks;
switch (this.filter.value) { switch (this.filter.value) {
case "active": case "active":
return this.tasks.filter((t) => !t.isCompleted); return tasks.filter((t) => !t.isCompleted);
case "completed": case "completed":
return this.tasks.filter((t) => t.isCompleted); return tasks.filter((t) => t.isCompleted);
case "all": case "all":
return this.tasks; return tasks;
} }
} }
setFilter(filter) { setFilter(filter) {
this.filter.value = filter; this.filter.value = filter;
} }
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Setup code // Setup
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
function makeStore() { const env = { store: createTaskStore() };
const localState = window.localStorage.getItem("todoapp"); mount(Root, document.body, { dev: true, env });
const state = localState ? JSON.parse(localState) : initialState;
const store = new Store({ state, actions });
store.on("update", null, () => {
localStorage.setItem("todoapp", JSON.stringify(store.state));
});
return store;
}
function setup() {
owl.config.mode = "dev";
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}
whenReady(setup);
})(); })();
``` ```
+68
View File
@@ -0,0 +1,68 @@
# 🦉 Notes On Owl Architecture 🦉
We explain here how Owl is designed
Warning: these notes are technical by nature, and intended for people working
on Owl (or interested in understanding its design).
## Overview
Roughly speaking, Owl has 5 main parts:
- a virtual dom system (in `src/blockdom`)
- a component system (in `src/component`)
- a template compiler (located in the `src/compiler` folder)
- a small runtime code to tie them together (in `src/app`)
- a reactivity system (in `src/reactivity.ts`)
There are some other files, but the core of Owl can be understood with these
five main parts.
The virtual dom is an optimized virtual dom based on blocks, which supports
multi blocks (for fragments). Everything that owl renders is internally
represented by a virtual node. The job of the virtual dom is to efficiently
represent the current state of the application, and to build an actual DOM
representation when needed, or update the DOM whenever it is needed.
- some other helpers/smaller scale stuff
A rendering occurs in two phases:
- virtual rendering: this generates the virtual dom in memory, asynchronously
- patch: applies a virtual tree to the screen (synchronously)
There are several classes involved in a rendering:
- components
- a scheduler
- fibers: small objects containing some metadata, associated with a rendering of
a specific component
Components are organized in a dynamic component tree, visible in the user
interface. Whenever a rendering is initiated in a component `C`:
- a fiber is created on `C` with the rendering props information
- the virtual rendering phase starts on C (will asynchronously render all the
child components)
- the fiber is added to the scheduler, which will poll continuously, every
animation frame, if the fiber is done
- once it is done, the scheduler will call the task callback, which will apply
the patch (if it was not cancelled in the meantime).
# 🦉 VDom 🦉
Owl is a declarative component system: we declare the structure of the component
tree, and Owl will translate that to a list of imperative operations. This
translation is done by a virtual dom. This is the low level layer of Owl, most
developer will not need to call directly the virtual dom functions.
The main idea behind a virtual dom is to keep a in-memory representation of the
DOM (called a virtual node), and whenever some change is needed, to regenerate
a new representation, compute the difference between the old and the new, then
apply the changes.
`vdom` exports two functions:
- `h`: create a new virtual node
- `patch`: compare two virtual nodes, and apply the difference.
Note: Owl's virtual dom is a fork of [snabbdom](https://github.com/snabbdom/snabbdom).
+4 -5
View File
@@ -14,8 +14,7 @@ discussed, feel free to open an issue/submit a PR to correct this text.
- [Tooling/Build Step](#toolingbuild-step) - [Tooling/Build Step](#toolingbuild-step)
- [Templating](#templating) - [Templating](#templating)
- [Asynchronous rendering](#asynchronous-rendering) - [Asynchronous rendering](#asynchronous-rendering)
- [Reactiveness](#reactiveness) - [Reactivity](#reactivity)
- [State Management](#state-management)
- [Hooks](#hooks) - [Hooks](#hooks)
## Size ## Size
@@ -79,7 +78,7 @@ additional tools, we made a lot of effort to make the most of the web platform.
For example, Owl uses the standard `xml` parser that comes with every browser. For example, Owl uses the standard `xml` parser that comes with every browser.
Because of that, Owl did not have to write its own template parser. Another Because of that, Owl did not have to write its own template parser. Another
example is the [`xml`](../reference/tags.md#xml-tag) tag helper function, which makes use of example is the [`xml`](../reference/templates.md#inline-templates) tag helper function, which makes use of
native template literals to allow in a natural way to write `xml` templates native template literals to allow in a natural way to write `xml` templates
directly in the javascript code. This can be easily integrated with editor directly in the javascript code. This can be easily integrated with editor
plugins to have autocompletion inside the template. plugins to have autocompletion inside the template.
@@ -127,7 +126,7 @@ structured than a template language. Note that the tooling is quite impressive:
there is a syntax highlighter for jsx here on github! there is a syntax highlighter for jsx here on github!
By comparison, here is the equivalent Owl component, written with the By comparison, here is the equivalent Owl component, written with the
[`xml`](../reference/tags.md#xml-tag) tag helper: [`xml`](../reference/templates.md#inline-templates) tag helper:
```js ```js
class Clock extends Component { class Clock extends Component {
@@ -173,7 +172,7 @@ more convoluted. For example, in Vue, you need to use a dynamic import keyword
that needs to be transpiled at build time in order for the component to be loaded that needs to be transpiled at build time in order for the component to be loaded
asynchronously (see [the documentation](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components)). asynchronously (see [the documentation](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components)).
## Reactiveness ## Reactivity
React has a simple model: whenever the state changes, it is React has a simple model: whenever the state changes, it is
replaced with a new state (via the `setState` method). Then, the DOM is patched. replaced with a new state (via the `setState` method). Then, the DOM is patched.
+98
View File
@@ -0,0 +1,98 @@
# 🦉 Notes On Owl Compiled Templates 🦉
This page will explain what an Owl compiled template look like. This is a
technical document intended for developers interested in understanding how Owl
works internally.
Broadly speaking, Owl compiles templates into a javascript function (a closure)
that returns a function (the "render" function). The point of the closure is to
have a place to store all values specific to the template (in particular, "blocks").
Once a template is compiled, its closure function is called once to get the
render function, and from then on, only the render function is used.
The render function takes some context (and some additional information) and
return a virtual dom representation of the rendered template, as a block tree.
A block tree is a very light weight representation that only contains the dynamic
part of the template, and its structure. It is actually independant of the
static part of the templates (which are contained in the blocks captured by the
closure). This means that the work performed at render time is only to collect
dynamic data, and to describe the block structure of the result.
It looks like this, in pseudo code:
```js
function closure(bdom, helpers) {
// here is some place to put stuff specific to the template, such as
// blocks
...
return function render(context, node, key) {
// only build here all dynamic parts of the template
// build a block tree
return tree;
}
}
```
Now, let us see an example. Consider the following template:
```xml
<div class="some-class">
<div class="blabla">
<span><t t-esc="state.value"/></span>
</div>
<t t-if="state.info">
<p class="info" t-att-class="someAttribute">
<t t-esc="state.info"/>
</p>
</t>
<SomeComponent value="value"/>
</div>
```
If you look carefully, there are 5 dynamic things:
- a text value (the first `t-esc`),
- a sub block (the `t-if`),
- a dynamic attribute (the `t-att-class` attribute),
- another text value (the second `t-esc`),
- and finally, a sub component
Here is the compiled code for this template:
```js
function closure(bdom, helpers) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(
`<div class="some-class"><div class="blabla"><span><block-text-0/></span></div><block-child-0/><block-child-1/></div>`
);
let block2 = createBlock(`<p class="info" block-attribute-0="class"><block-text-1/></p>`);
return function render(ctx, node, key = "") {
let b2, b3;
let txt1 = ctx["state"].value;
if (ctx["state"].info) {
let attr1 = ctx["someAttribute"];
let txt2 = ctx["state"].info;
b2 = block2([attr1, txt2]);
}
b3 = component(`SomeComponent`, { value: ctx["value"] }, key + `__1`, node, ctx);
return block1([txt1], [b2, b3]);
};
}
```
The values captured in the closure capture the static part of the template: we
define here two blocks (which contains a template node, that can be deep cloned
whenever a block is mounted). Then the render function only describes the block
tree structure of the result, depending on the context. This means that we
minimize the amount of work done at render time.
Then, when we want to patch the dom, Owl will uses the `patch` function from
blockdom, which then will diff the block tree, and deep clone new blocks whenever
a new block is inserted, keep track of dynamic parts of each block, and update
them accordingly.
With this design, the cost of rendering a template is proportional to the number
of dynamic values, and not to the size of the template.
-32
View File
@@ -1,32 +0,0 @@
# 🦉 Rendering Pipeline 🦉
We explain here how Owl is designed, from the perspective of its rendering
pipeline.
Warning: these notes are technical by nature, and intended for people working
on Owl (or interested in understanding its design).
## Overview
A rendering occurs in two phases:
- virtual rendering: this generates the virtual dom in memory, asynchronously
- patch: applies a virtual tree to the screen (synchronously)
There are several classes involved in a rendering:
- components
- a scheduler
- fibers: small objects containing some metadata, associated with a rendering of
a specific component
Components are organized in a dynamic component tree, visible in the user
interface. Whenever a rendering is initiated in a component `C`:
- a fiber is created on `C` with the rendering props information
- the virtual rendering phase starts on C (will asynchronously render all the
child components)
- the fiber is added to the scheduler, which will poll continuously, every
animation frame, if the fiber is done
- once it is done, the scheduler will call the task callback, which will apply
the patch (if it was not cancelled in the meantime).
-18
View File
@@ -1,18 +0,0 @@
# 🦉 VDom 🦉
Owl is a declarative component system: we declare the structure of the component
tree, and Owl will translate that to a list of imperative operations. This
translation is done by a virtual dom. This is the low level layer of Owl, most
developer will not need to call directly the virtual dom functions.
The main idea behind a virtual dom is to keep a in-memory representation of the
DOM (called a virtual node), and whenever some change is needed, to regenerate
a new representation, compute the difference between the old and the new, then
apply the changes.
`vdom` exports two functions:
- `h`: create a new virtual node
- `patch`: compare two virtual nodes, and apply the difference.
Note: Owl's virtual dom is a fork of [snabbdom](https://github.com/snabbdom/snabbdom).
+37 -43
View File
@@ -1,54 +1,48 @@
# 🦉 OWL Documentation 🦉 # 🦉 Owl overview 🦉
## Learning Owl Here is a list of everything exported by the Owl library:
Are you new to Owl? This is the place to start! Main entities:
- [Tutorial: create a TodoList application](learning/tutorial_todoapp.md) - [`App`](reference/app.md): represent an Owl application (mainly a root component,a set of templates, and a config)
- [Quick Overview](learning/overview.md) - [`Component`](reference/component.md): the main class to define a concrete Owl component
- [How to start an Owl project](learning/quick_start.md) - [`mount`](reference/app.md#mount-helper): main entry point for most application: mount a component to a target
- [How to test Components](learning/how_to_test.md) - [`xml`](reference/templates.md#inline-templates): helper to define an inline template
- [How to write Single File Components](learning/how_to_write_sfc.md)
## Reference Reactivity
You will find here a complete reference of every feature, class or object - [`useState`](reference/reactivity.md#usestate): create a reactive object (hook, linked to a specific component)
provided by Owl. - [`reactive`](reference/reactivity.md#reactive): create a reactive object (not linked to any component)
- [`markRaw`](reference/reactivity.md#markraw): mark an object or array so that it is ignored by the reactivity system
- [`toRaw`](reference/reactivity.md#toraw): given a reactive objet, return the raw (non reactive) underlying object
- [Animations](reference/animations.md) Lifecycle hooks:
- [Browser](reference/browser.md)
- [Component](reference/component.md)
- [Content](reference/content.md)
- [Concurrency Model](reference/concurrency_model.md)
- [Configuration](reference/config.md)
- [Context](reference/context.md)
- [Environment](reference/environment.md)
- [Event Bus](reference/event_bus.md)
- [Event Handling](reference/event_handling.md)
- [Error Handling](reference/error_handling.md)
- [Hooks](reference/hooks.md)
- [Mounting a component](reference/mounting.md)
- [Miscellaneous Components](reference/misc.md)
- [Observer](reference/observer.md)
- [Props](reference/props.md)
- [Props Validation](reference/props_validation.md)
- [QWeb Templating Language](reference/qweb_templating_language.md)
- [QWeb Engine](reference/qweb_engine.md)
- [Slots](reference/slots.md)
- [Tags](reference/tags.md)
- [Utils](reference/utils.md)
## Other Topics - [`onWillStart`](reference/component.md#willstart): hook to define asynchronous code that should be executed before component is rendered
- [`onMounted`](reference/component.md#mounted): hook to define code that should be executed when component is mounted
- [`onWillPatch`](reference/component.md#willpatch): hook to define code that should be executed before component is patched
- [`onWillUpdateProps`](reference/component.md#willupdateprops): hook to define code that should be executed before component is updated
- [`onPatched`](reference/component.md#patched): hook to define code that should be executed when component is patched
- [`onWillRender`](reference/component.md#willrender): hook to define code that should be executed before component is rendered
- [`onRendered`](reference/component.md#rendered): hook to define code that should be executed after component is rendered
- [`onWillUnmount`](reference/component.md#willunmount): hook to define code that should be executed before component is unmounted
- [`onWillDestroy`](reference/component.md#willdestroy): hook to define code that should be executed before component is destroyed
- [`onError`](reference/component.md#onerror): hook to define a Owl error handler
This section provides miscellaneous document that explains some topics Other hooks:
which cannot be considered either a tutorial, or reference documentation.
- [Owl architecture: the Virtual DOM](miscellaneous/vdom.md) - [`useComponent`](reference/hooks.md#usecomponent): return a reference to the current component (useful to create derived hooks)
- [Owl architecture: the rendering pipeline](miscellaneous/rendering.md) - [`useEffect`](reference/hooks.md#useeffect): define an effect with its dependencies
- [Comparison with React/Vue](miscellaneous/comparison.md) - [`useEnv`](reference/hooks.md#useenv): return a reference to the current env
- [Why did Odoo built Owl?](miscellaneous/why_owl.md) - [`useExternalListener`](reference/hooks.md#useexternallistener): add a listener outside of a component DOM
- [`useRef`](reference/hooks.md#useref): get an object representing a reference (`t-ref`)
- [`useChildSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for child components)
- [`useSubEnv`](reference/hooks.md#usesubenv-and-usechildsubenv): extend the current env with additional information (for current component and child components)
--- Utility/helpers:
Found an issue in the documentation? A broken link? Some outdated information? - [`EventBus`](reference/utils.md#eventbus): a simple event bus
Please open an issue or submit a PR! - [`loadFile`](reference/utils.md#loadfile): an helper to load a file from the server
- [`markup`](reference/templates.md#outputting-data): utility function to define strings that represent html (should not be escaped)
- [`status`](reference/component.md#status-helper): utility function to get the status of a component (new, mounted or destroyed)
- [`whenReady`](reference/utils.md#whenready): utility function to execute code when DOM is ready
-127
View File
@@ -1,127 +0,0 @@
# 🦉 Animations 🦉
Animation is a complex topic. There are many different use cases, and many
solutions and technologies. Owl only supports some basic use cases.
## Simple CSS effects
Sometimes, using pure CSS is enough. For these use cases, Owl is not really
necessary: it just needs to render a DOM element with a specific class. For
example:
```xml
<a class="btn flash" t-on-click="doSomething">Click</a>
```
with the following CSS:
```css
btn {
background-color: gray;
}
.flash {
transition: background 0.5s;
}
.flash:active {
background-color: #41454a;
transition: background 0s;
}
```
will produce a nice flash effect whenever the user clicks (or activates with the
keyboard) the button.
## CSS Transitions
A more complex situation occurs when we want to transition an element in or out
of the page. For example, we may want a fade-in and fade-out effect.
The `t-transition` directive is here to help us. It works on html elements and
on components, by adding and removing some css classes.
To perform useful transition effects, whenever an element appears or disappears,
it is necessary to add/remove some css style or class at some precise moment in
the lifetime of a node. Since this is not easy to do by hand, Owl `t-transition`
directive is there to help.
Whenever a node has a `t-transition` directive, with a `name` value, the following
sequence of events will happen:
At node insertion:
- the css classes `name-enter` and `name-enter-active` will be added directly
when the node is inserted into the DOM.
- on the next animation frame: the css class `name-enter` will be removed and the
class `name-enter-to` will be added (so they can be used to trigger css
transition effects).
- at the end of the transition, `name-enter-to` and `name-enter-active` will be removed.
At node destruction:
- the css classes `name-leave` and `name-leave-active` will be added before the
node is removed to the DOM.
- on the next animation frame: the css class `name-leave` will be removed and the
class `name-leave-to` will be added (so they can be used to trigger css
transition effects).
- at the end of the transition, `name-leave-to` and `name-leave-active` will be removed.
For example, a simple fade in/out effect can be done with this:
```xml
<div>
<div t-if="state.flag" class="square" t-transition="fade">Hello</div>
</div>
```
```css
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
```
The `t-transition` directive can be applied on a node element or on a component.
Notes:
Owl does not support more than one transition on a single node, so the
`t-transition` expression must be a single value (i.e. no space allowed).
## SCSS Mixins
If you use SCSS, you can use mixins to make generic animations. Here is an exemple with a fade in / fade out animation:
```scss
@mixin animation-fade($time, $name) {
.#{$name}_fade-enter-active,
.#{$name}_fade-active {
transition: all $time;
}
.#{$name}_fade-enter {
opacity: 0;
}
.#{$name}_fade-leave-to {
opacity: 0;
}
}
```
Usage:
```scss
@include animation-fade(0.5s, "o_notification");
```
You can now have in your template:
```xml
<SomeTag t-transition="o_notification_fade"/>
```
+112
View File
@@ -0,0 +1,112 @@
# 🦉 App 🦉
## Content
- [Overview](#overview)
- [API](#api)
- [Configuration](#configuration)
- [`mount` helper](#mount-helper)
- [Loading templates](#loading-templates)
## Overview
Every Owl application has a root element, a set of templates, an environment and
possibly a few other settings. The `App` class is a simple class that represents
all of these elements. Here is an example:
```js
const {Component, App } = owl;
class MyComponent extends Component { ... }
const app = new App(MyComponent, { props: {...}, templates: "..."});
app.mount(document.body);
```
The basic workflow is: create an `App` instance configured with the root
component, the templates, and possibly other settings. Then, we mount that
instance somewhere in the DOM.
## API
- **`constructor(Root[, config])`**: first argument should be a component class (not
an instance), and the optional second argument is a configuration object (see below).
- **`mount(target, options)`**: first argument is an html element, and the optional
second argument is an object with mounting options (see below). Mount the app
to a target in the DOM. Note that this is an asynchronous operation: the `mount`
method returns a promise that resolves to the component instance whenever it
is complete.
The `option` object is an object with the following keys:
- **`position (string)`**: either `first-child` or `last-child`. This option determines
the position of the application in the target: either first or last child.
- **`destroy()`**: destroys the application
## Configuration
The `config` object is an object with some of the following keys:
- **`env (object)`**: if given, this will be the shared `env` given to each component
- **`props (object)`**: the props given to the root component
- **`dev (boolean, default=false)`**: if `true`, the application is rendered in `dev`
mode, which activates some additional checks (in particular, the props validation
code is only performed in dev mode)
- **`test (boolean, default=false)`**: `test` mode is the same as `dev` mode, except
that Owll will not log a message to warn that Owl is in `dev` mode.
- **`translatableAttributes (string[])`**: a list of additional attributes that should
be translated (see [translations](translations.md))
- **`translateFn (function)`**: a function that will be called by owl to translate
templates (see [translations](translations.md))
- **`templates (string | xml document)`**: all the templates that will be used by
the components created by the application.
## `mount` helper
Note that there is a `mount` helper to do that in just a line:
```js
const { mount, Component } = owl;
class MyComponent extends Component {
...
}
mount(MyComponent, document.body, { props: {...}, templates: "..."});
```
Here is the `mount` function signature:
**`mount(Component, target, config)`** with the following arguments:
- **`Component`**: a component class (Root component of the app)
- **`target`**: an html element, where the component will be mounted as last child
- **`config (optional)`**: a config object (the same as the App config object)
Most of the time, the `mount` helper is more convenient, but whenever one needs
a reference to the actual Owl App, then using the `App` class directly is
possible.
## Loading templates
Most applications will need to load templates whenever they start. Here is
what it could look like in practice:
```js
// in the main js file:
const { loadFile, mount } = owl;
// async, so we can use async/await
(async function setup() {
const templates = await loadFile(`/some/endpoint/that/return/templates`);
const env = {
_t: someTranslateFn,
templates,
// possibly other stuff
};
mount(Root, document.body, { env });
})();
```
-33
View File
@@ -1,33 +0,0 @@
# 🦉 Browser 🦉
## Content
- [Overview](#overview)
- [Browser Content](#browser-content)
## Overview
The browser object contains some browser native APIs, such as `setTimeout`, that
are used by Owl and its utility functions. They are exposed with the intent of
making them mockable if necessary.
```js
owl.browser.setTimeout === window.setTimeout; // return true
```
For now, this object contains some functions that are not used by Owl. They
will eventually be removed in Owl 2.0.
## Browser Content
More specifically, the `browser` object contains the following methods and objects:
- `setTimeout`
- `clearTimeout`
- `setInterval`
- `clearInterval`
- `requestAnimationFrame`
- `random`
- `Date`
- `fetch`
- `localStorage`
+271 -672
View File
File diff suppressed because it is too large Load Diff
+6 -24
View File
@@ -11,7 +11,7 @@
Owl was designed from the very beginning with asynchronous components. This comes Owl was designed from the very beginning with asynchronous components. This comes
from the `willStart` and the `willUpdateProps` lifecycle hooks. With these from the `willStart` and the `willUpdateProps` lifecycle hooks. With these
methods, it is possible to build complex highly concurrent applications. asynchronous hooks, it is possible to build complex highly concurrent applications.
Owl concurrent mode has several benefits: it makes it possible to delay the Owl concurrent mode has several benefits: it makes it possible to delay the
rendering until some asynchronous operation is complete, it makes it possible rendering until some asynchronous operation is complete, it makes it possible
@@ -35,7 +35,8 @@ two phases: _virtual rendering_ and _patching_.
### Virtual rendering ### Virtual rendering
This phase represent the process of rendering a template, in memory, which create a virtual representation of the desired component html). The output of this phase is a This phase represent the process of rendering a template, in memory, which creates
a virtual representation of the desired component html). The output of this phase is a
virtual DOM. virtual DOM.
It is asynchronous: each subcomponents needs to either be created (so, `willStart` It is asynchronous: each subcomponents needs to either be created (so, `willStart`
@@ -94,7 +95,7 @@ component (with some code like `app.mount(document.body)`).
5. The method `mounted` is called recursively on all components in the following 5. The method `mounted` is called recursively on all components in the following
order: `E`, `D`, `C`, `B`, `A`. order: `E`, `D`, `C`, `B`, `A`.
**Scenario 2: rerendering a component**. Now, let's assume that the user clicked on some **Scenario 2: updating a component**. Now, let's assume that the user clicked on some
button in `C`, and this results in a state update, which is supposed to: button in `C`, and this results in a state update, which is supposed to:
- update `D`, - update `D`,
@@ -136,8 +137,7 @@ Here is what Owl will do:
6. `mounted` hook is called on `F`, `patched` hooks are called on `D`, `C` 6. `mounted` hook is called on `F`, `patched` hooks are called on `D`, `C`
Tags are very small helpers to make it easy to write inline templates. There is Tags are very small helpers to make it easy to write inline templates. There is
only one currently available tag: `xml`, but we plan to add other tags later, only one currently available tag: `xml`.
such as a `css` tag, which will be used to write [single file components](../learning/how_to_write_sfc.md).
### Asynchronous Rendering ### Asynchronous Rendering
@@ -160,22 +160,4 @@ Here are a few tips on how to work with asynchronous components:
1. Minimize the use of asynchronous components! 1. Minimize the use of asynchronous components!
2. Lazy loading external libraries is a good use case for async rendering. This 2. Lazy loading external libraries is a good use case for async rendering. This
is mostly fine, because we can assume that it will only takes a fraction of a is mostly fine, because we can assume that it will only takes a fraction of a
second, and only once (see [`owl.utils.loadJS`](utils.md#loadjs)) second, and only once.
3. For all the other cases, the [`AsyncRoot`](misc.md#asyncroot) component is there to help you. When
this component is met, a new rendering
sub tree is created, such that the rendering of that component (and its
children) is not tied to the rendering of the rest of the interface. It can
be used on an asynchronous component, to prevent it from delaying the
rendering of the whole interface, or on a synchronous one, such that its
rendering isn't delayed by other (asynchronous) components. Note that this
directive has no effect on the first rendering, but only on subsequent ones
(triggered by state or props changes).
```xml
<div t-name="ParentComponent">
<SyncChild />
<AsyncRoot>
<AsyncChild/>
</AsyncRoot>
</div>
```
-44
View File
@@ -1,44 +0,0 @@
# 🦉 Config 🦉
The Owl framework is designed to work in many situations. However, it is
sometimes necessary to customize some behaviour. This is done by using the
global `config` object. It provides two settings:
- [`mode`](#mode) (default value: `prod`),
- [`enableTransitions`](#enabletransitions) (default value: `true`).
## Mode
By default, Owl is in _production_ mode, this means that it will try to do its
job fast, and skip some expensive operations. However, it is sometimes necessary
to have better information on what is going on, this is the purpose
of the `dev` mode.
Owl has a mode flag, in `owl.config.mode`. Its default value is `prod`, but
it can be set to `dev`:
```js
owl.config.mode = "dev";
```
Note that templates compiled with the `prod` settings will not be recompiled.
So, changing this setting is best done at startup.
An important job done by the `dev` mode is to validate props for each component
creation and update. Also, extra props will cause an error.
## `enableTransitions`
Transitions are usually nice, but they can cause issues in some specific cases,
such as automated tests. It is uncomfortable having to wait for a transition
to end before moving to the next step.
To solve this issue, Owl can be configured to ignore the `t-transition` directive.
To do that, one only needs to set the `enableTransitions` flag to false:
```js
owl.config.enableTransitions = false;
```
Note that it suffers from the same drawback as the "dev" mode: all compiled
templates, if any, will keep their current behaviours.
-36
View File
@@ -1,36 +0,0 @@
# 🦉 Owl Content 🦉
Here is a complete visual representation of everything exported by the `owl`
global object.
For example, `Component` is available at `owl.Component` and `EventBus` is
exported as `owl.core.EventBus`.
```
browser
Component misc
Context AsyncRoot
QWeb Portal
mount
useState tags
config css
mode xml
core utils
EventBus debounce
Observer escape
hooks loadJS
onWillStart loadFile
onMounted shallowEqual
onWillUpdateProps whenReady
onWillPatch
onPatched
onWillUnmount
useContext
useState
useRef
useComponent
useEnv
useSubEnv
```
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
+23 -83
View File
@@ -6,15 +6,15 @@
- [Setting an Environment](#setting-an-environment) - [Setting an Environment](#setting-an-environment)
- [Using a sub environment](#using-a-sub-environment) - [Using a sub environment](#using-a-sub-environment)
- [Content of an Environment](#content-of-an-environment) - [Content of an Environment](#content-of-an-environment)
- [Special keys](#special-keys)
## Overview ## Overview
An environment is an object which contains a [`QWeb` instance](qweb_engine.md). Whenever An environment is a shared object given to all components in a tree. It is not
a root component is created, it is assigned an environment (see used by Owl itself, but it is useful for application developers to provide a
[below](#setting-an-environment) for more info on this). This environment is simple communication channel between components (in addition to the props).
then automatically given to each sub component (and accessible in the `this.env`
property). The `env` given to the [`App`](app.md) is assigned to the `env` component
property.
``` ```
Root Root
@@ -22,31 +22,14 @@ property).
A B A B
``` ```
This way, all components share the same `QWeb` instance. Owl internally requires Also, the `env` object is frozen when the application is started. This is done
that the environment has a `qweb` key which maps to a to ensure a simpler mental model of what's happening in runtime. Note that it
[`QWeb`](qweb_engine.md) instance. This is the QWeb instance that will be used to is only shallowly frozen, so sub objects can be modified.
render each templates in this specific component tree. Note that if no `QWeb`
instance is provided, Owl will simply generate it on the fly.
The environment is mostly static. Each application is free to add anything to
the environment, which is very useful, since this can be accessed by each sub
component.
## Setting an environment ## Setting an environment
An Owl application needs an [environment](environment.md) to be executed. The The correct way to customize an environment is to simply give it to the `App`,
environment has an important key: the [QWeb](qweb_engine.md) instance, which will render whenever it is created.
all templates.
Whenever a root component `App` is mounted, Owl will setup a valid environment by
following the next steps:
- take the `env` object defined on `App.env` (if no `env` was explicitly setup,
this will return the empty `env` object defined on `Component`)
- if `env.qweb` is not set, then Owl will create a `QWeb` instance.
The correct way to customize an environment is to simply set it up on the root
component class, before the first component is created:
```js ```js
const env = { const env = {
@@ -56,81 +39,38 @@ const env = {
... ...
}, },
}; };
mount(App, { target: document.body, env });
new App(Root, { env }).mount(document.body);
// or alternatively
mount(App, document.body, { env });
``` ```
It is also possible to simply share an environment between all root components,
by simply doing this:
```js
Component.env = myEnv; // will be the default env for all components
```
Note that this environment is the global owl environment for an application. The
next section explains how to extend an environment for a specific sub component
and its children.
## Using a sub environment ## Using a sub environment
It is sometimes useful to add one (or more) specific keys to the environment, It is sometimes useful to add one (or more) specific keys to the environment,
from the perspective of a specific component and its children. In that case, the from the perspective of a specific component and its children. In that case, the
solution presented above will not work, since it sets the global environment. solution presented above will not work, since it sets the global environment.
There is a hook for this situation: [`useSubEnv`](hooks.md#usesubenv). There are two hooks for this situation: [`useSubEnv` and `useChildSubEnv`](hooks.md#usesubenv-and-usechildsubenv).
```js ```js
class FormComponent extends Component { class SomeComponent extends Component {
constructor(parent, props) { setup() {
super(parent, props); useSubEnv({ myKey: someValue }); // myKey is now available for all child components
useSubEnv({ myKey: someValue });
} }
} }
``` ```
## Content of an Environment ## Content of an Environment
Some good use cases for additional keys in the environment are: The `env` object content is totally up to the application developer. However,
some good use cases for additional keys in the environment are:
- some configuration keys, - some configuration keys,
- session information, - session information,
- generic services (such as doing rpcs). - generic services (such as doing rpcs).
- other utility functions that one want to inject, such as a translation function.
Doing it this way means that components are easily testable: we can simply Doing it this way means that components are easily testable: we can simply
create a test environment with mock services. create a test environment with mock services.
For example:
```js
async function myEnv() {
const templates = await loadTemplates();
const qweb = new QWeb({ templates });
const session = getSession();
return {
_t: myTranslateFunction,
session: session,
qweb: qweb,
services: {
localStorage: localStorage,
rpc: rpc,
},
debug: false,
inMobileMode: true,
};
}
async function start() {
const env = await myEnv();
mount(App, { target: document.body, env });
}
```
## Special Keys
There are two special key/value added by Owl if not provided in the environment:
the `QWeb` instance and a `browser` object:
- `qweb` will be set to an empty `QWeb` instance. This is absolutely necessary
for Owl to be able to render anything
- `browser`: this is an object that contains some common access points to the
browser methods with a side effect. See [browser](browser.md) for more information. Note that the browser object will be removed from the environment in Owl 2.0.
+44 -50
View File
@@ -3,60 +3,30 @@
## Content ## Content
- [Overview](#overview) - [Overview](#overview)
- [Managing Errors](#managing-errors)
- [Example](#example) - [Example](#example)
- [Reference](#reference)
## Overview ## Overview
By default, whenever an error occurs in the rendering of an Owl application, we By default, whenever an error occurs in the rendering of an Owl application, we
destroy the whole application. Otherwise, we cannot offer any guarantee on the destroy the whole application. Otherwise, we cannot offer any guarantee on the
state of the resulting component tree. It might be hopelessly corrupted, but state of the resulting component tree. It might be hopelessly corrupted, but
without any user-visible state. without any user-visible feedback.
Clearly, it sometimes is a little bit extreme to destroy the application. This Clearly, it is usually a little bit extreme to destroy the application. This
is why we have a builtin mechanism to handle rendering errors (and errors coming is why we need a mechanism to handle rendering errors (and errors coming
from lifecycle hooks): the `catchError` hook. from lifecycle hooks): the `onError` hook.
## Example The main idea is that the `onError` hook register a function that will be called
with the error. This function need to handle the situation, most of the time by
updating some state and rerendering itself, so the application can return to a
normal state.
For example, here is how we could implement an `ErrorBoundary` component: ## Managing Errors
```xml Whenever the `onError` lifecycle hook is used, all errors coming from
<div t-name="ErrorBoundary">
<t t-if="state.error">
Error handled
</t>
<t t-else="">
<t t-slot="default" />
</t>
</div>
```
```js
class ErrorBoundary extends Component {
state = useState({ error: false });
catchError() {
this.state.error = true;
}
}
```
Using the `ErrorBoundary` is then extremely simple:
```xml
<ErrorBoundary><SomeOtherComponent/></ErrorBoundary>
```
Note that we need to be careful here: the fallback UI should not throw any
error, otherwise we risk going into an infinite loop (also, see the page on
[slots](slots.md) for more information on the `t-slot` directive).
## Reference
Whenever the `catchError` lifecycle hook is implemented, all errors coming from
sub components rendering and/or lifecycle method calls will be caught and given sub components rendering and/or lifecycle method calls will be caught and given
to the `catchError` method. This allows us to properly handle the error, and to to the `onError` method. This allows us to properly handle the error, and to
not break the application. not break the application.
There are important things to know: There are important things to know:
@@ -65,17 +35,41 @@ There are important things to know:
Owl will destroy the full application. This is done on purpose, because Owl Owl will destroy the full application. This is done on purpose, because Owl
cannot guarantee that the state is not corrupted from this point on. cannot guarantee that the state is not corrupted from this point on.
- errors coming from event handlers are NOT managed by `catchError` or any other - errors coming from event handlers are NOT managed by `onError` or any other
owl mechanism. This is up to the application developer to properly recover owl mechanism. This is up to the application developer to properly recover
from an error from an error
Also, it may be useful to know that whenever an error is caught, it is then - if an error handler is unable to properly handle an error, it can just rethrow
broadcasted to the application by an event on the `qweb` instance. It may be an error, and Owl will try looking for another error handler up the component
useful, for example, to log the error somewhere. tree.
## Example
For example, here is how we could implement a generic component `ErrorBoundary`
that render its content, and a fallback if an error happened.
```js ```js
env.qweb.on("error", null, function (error) { class ErrorBoundary extends Component {
// do something static template = xml`
// react to the error <t t-if="error" t-slot="fallback">An error occurred</t>
}); <t t-else="" t-slot="content"`;
setup() {
this.state = useState({ error: false });
onError(() => (this.state.error = true));
}
}
``` ```
Using the `ErrorBoundary` is then simple simple:
```xml
<ErrorBoundary>
<SomeOtherComponent/>
<t t-set-slot="fallback">Some specific error message</t>
</ErrorBoundary>
```
Note that we need to be careful here: the fallback UI should not throw any
error, otherwise we risk going into an infinite loop (also, see the page on
[slots](slots.md) for more information on the `t-slot` directive).
-25
View File
@@ -1,25 +0,0 @@
# 🦉 Event Bus 🦉
It is sometimes useful to use a `Bus` to communicate informations between various
parts of the code. Owl has a very simple bus class, which manages subscriptions,
triggering events, and callbacks.
```js
const bus = new owl.core.EventBus();
bus.on("some-event", null, function (...args) {
console.log(...args);
});
bus.trigger("some-event", 1, 2, 3);
// [1,2,3] will be logged to the console
```
Its API is:
| Method | Description |
| -------------------------------- | --------------------------------- |
| `on(eventType, owner, callback)` | add a listener |
| `off(eventType, owner)` | remove all listeners for an owner |
| `trigger(eventType, ...args)` | trigger an event |
| `clear` | remove all subscriptions |
+46 -95
View File
@@ -3,22 +3,14 @@
## Content ## Content
- [Event Handling](#event-handling) - [Event Handling](#event-handling)
- [Business DOM Events](#business-dom-events)
- [Inline Event Handlers](#inline-event-handlers)
- [Modifiers](#modifiers) - [Modifiers](#modifiers)
- [Synthetic Events](#synthetic-events)
## Event Handling ## Event Handling
In a component's template, it is useful to be able to register handlers on DOM In a component's template, it is useful to be able to register handlers on DOM
elements to some specific events. This is what makes a template _alive_. There elements to some specific events. This is what makes a template _alive_. This
are four different use cases. is done with the `t-on` directive. For example:
1. Register an event handler on a DOM node (_pure_ DOM event)
2. Register an event handler on a component (_pure_ DOM event)
3. Register an event handler on a DOM node (_business_ DOM event)
4. Register an event handler on a component (_business_ DOM event)
A _pure_ DOM event is directly triggered by a user interaction (e.g. a `click`).
```xml ```xml
<button t-on-click="someMethod">Do something</button> <button t-on-click="someMethod">Do something</button>
@@ -31,93 +23,28 @@ button.addEventListener("click", component.someMethod.bind(component));
``` ```
The suffix (`click` in this example) is simply the name of the actual DOM The suffix (`click` in this example) is simply the name of the actual DOM
event. event. The value of the `t-on` expression should be a valid javascript expression
that evaluates to a function in the context of the current component. So, one
## Business DOM Events can get a reference to the event, or pass some additional arguments. For example,
all the following expressions are valid:
A _business_ DOM event is triggered by a call to `trigger` on a component.
```xml ```xml
<MyComponent t-on-menu-loaded="someMethod" /> <button t-on-click="someMethod">Do something</button>
<button t-on-click="() => this.increment(3)">Add 3</button>
<button t-on-click="ev => this.doStuff(ev, 'value')">Do something</button>
``` ```
```js Notice the use of the `this` keyword in the lambda function: this is the
class MyComponent { correct way to call a method on the component in a lambda function.
someWhere() {
const payload = ...;
this.trigger('menu-loaded', payload);
}
}
```
The call to `trigger` generates an `OwlEvent`, a subclass of [_CustomEvent_](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) One could use the following expression:
with an additional attribute `originalComponent` (the component that triggered
the event). The generated event is of type `menu-loaded` and dispatches it on
the component's DOM element (`this.el`). The event bubbles and is cancelable.
The parent component listening to event `menu-loaded` will receive the payload
in its `someMethod` handler (in the `detail` property of the event), whenever
the event is triggered.
```js
class ParentComponent {
someMethod(ev) {
const payload = ev.detail;
...
}
}
```
By convention, we use KebabCase for the name of _business_ events.
The `t-on` directive allows to prebind its arguments. For example,
```xml ```xml
<button t-on-click="someMethod(expr)">Do something</button> <button t-on-click="() => increment(3)">Add 3</button>
``` ```
Here, `expr` is a valid Owl expression, so it could be `true` or some variable But then, the increment function may be unbound (unless the component binds it
from the rendering context. in its setup function, for example).
### Type Hinting
Note that if you work with Typescript, the `trigger` method is generic on the type of the payload.
You can then describe the type of the event, so you will see typing errors...
```typescript
this.trigger<MyCustomPayload>("my-custom-event", payload);
```
```typescript
myCustomEventHandler(ev: OwlEvent<MyCustomPayload>) { ... }
```
## Inline Event Handlers
One can also directly specify inline statements. For example,
```xml
<button t-on-click="state.counter++">Increment counter</button>
```
Here, `state` must be defined in the rendering context (typically the component)
as it will be translated to:
```js
button.addEventListener("click", () => {
context.state.counter++;
});
```
Warning: inline expressions are evaluated in the context of the template. This
means that they can access the component methods and properties. But if they set
a key, the inline statement will actually not modify the component, but a key in
a sub scope.
```xml
<button t-on-click="value = 1">Set value to 1 (does not work!!!)</button>
<button t-on-click="state.value = 1">Set state.value to 1 (work as expected)</button>
```
## Modifiers ## Modifiers
@@ -125,12 +52,13 @@ In order to remove the DOM event details from the event handlers (like calls to
`event.preventDefault`) and let them focus on data logic, _modifiers_ can be `event.preventDefault`) and let them focus on data logic, _modifiers_ can be
specified as additional suffixes of the `t-on` directive. specified as additional suffixes of the `t-on` directive.
| Modifier | Description | | Modifier | Description |
| ---------- | ------------------------------------------------------------------------------------------------------------------------ | | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `.stop` | calls `event.stopPropagation()` before calling the method | | `.stop` | calls `event.stopPropagation()` before calling the method |
| `.prevent` | calls `event.preventDefault()` before calling the method | | `.prevent` | calls `event.preventDefault()` before calling the method |
| `.self` | calls the method only if the `event.target` is the element itself | | `.self` | calls the method only if the `event.target` is the element itself |
| `.capture` | bind the event handler in [capture](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) mode. | | `.capture` | bind the event handler in [capture](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) mode. |
| `.synthetic` | define a synthetic event handler (see below) |
```xml ```xml
<button t-on-click.stop="someMethod">Do something</button> <button t-on-click.stop="someMethod">Do something</button>
@@ -149,3 +77,26 @@ modifiers. For example,
``` ```
This will simply stop the propagation of the event. This will simply stop the propagation of the event.
## Synthetic Events
In some cases, attaching an event handler for each element of large lists has
a non trivial cost. Owl provides a way to efficiently improve the performance:
with synthetic event, it actually adds only one handler on the document body,
and will properly call the handler, just as expected.
The only difference with regular events is that the event is caught at the document
body, so it cannot be stopped before it actually gets there. Since it may be
surprising in some cases, it is not enabled by default.
To enable it, one can just use the `.synthetic` suffix:
```xml
<div>
<t t-foreach="largeList" t-as="elem" t-key="elem.id">
<button t-on-click.synthetic="doSomething" ...>
<!-- some content -->
</button>
</t>
</div>
```
+168 -280
View File
@@ -3,24 +3,17 @@
## Content ## Content
- [Overview](#overview) - [Overview](#overview)
- [Example: Mouse Position](#example-mouse-position) - [The Hook Rule](#the-hook-rule)
- [Example: Autofocus](#example-autofocus) - [Lifecycle hooks](#lifecycle-hooks)
- [Reference](#reference) - [Other hooks](#other-hooks)
- [One Rule](#one-rule)
- [`useState`](#usestate) - [`useState`](#usestate)
- [`onMounted`](#onmounted)
- [`onWillUnmount`](#onwillunmount)
- [`onWillPatch`](#onwillpatch)
- [`onPatched`](#onpatched)
- [`onWillStart`](#onwillstart)
- [`onWillUpdateProps`](#onwillupdateprops)
- [`useContext`](#usecontext)
- [`useRef`](#useref) - [`useRef`](#useref)
- [`useSubEnv`](#usesubenv) - [`useSubEnv` and `useChildSubEnv`](#usesubenv-and-usechildsubenv)
- [`useExternalListener`](#useexternallistener) - [`useExternalListener`](#useexternallistener)
- [`useComponent`](#usecomponent) - [`useComponent`](#usecomponent)
- [`useEnv`](#useenv) - [`useEnv`](#useenv)
- [Making customized hooks](#making-customized-hooks) - [`useEffect`](#useeffect)
- [Example: Mouse Position](#example-mouse-position)
## Overview ## Overview
@@ -39,96 +32,9 @@ Hooks work beautifully with Owl components: they solve the problems mentioned
above, and in particular, they are the perfect way to make your component above, and in particular, they are the perfect way to make your component
reactive. reactive.
## Example: mouse position ## The Hook Rule
Here is the classical example of a non trivial hook to track the mouse position. There is only one rule: every hook for a component has to be called in the _setup_ method, or in class fields:
```js
const { useState, onMounted, onWillUnmount } = owl.hooks;
// We define here a custom behaviour: this hook tracks the state of the mouse
// position
function useMouse() {
const position = useState({ x: 0, y: 0 });
function update(e) {
position.x = e.clientX;
position.y = e.clientY;
}
onMounted(() => {
window.addEventListener("mousemove", update);
});
onWillUnmount(() => {
window.removeEventListener("mousemove", update);
});
return position;
}
// Main root component
class App extends owl.Component {
static template = xml`
<div t-name="App">
<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>
</div>`;
// this hooks is bound to the 'mouse' property.
mouse = useMouse();
}
```
Note that we use the prefix `use` for hooks, just like in React. This is just
a convention.
## Example: autofocus
Hooks can be combined to create the desired effect. For example, the following
hook combines the `useRef` hook with the `onPatched` and `onMounted` functions
to create an easy way to focus an input whenever it appears in the DOM:
```js
function useAutofocus(name) {
let ref = useRef(name);
let isInDom = false;
function updateFocus() {
if (!isInDom && ref.el) {
isInDom = true;
ref.el.focus();
} else if (isInDom && !ref.el) {
isInDom = false;
}
}
onPatched(updateFocus);
onMounted(updateFocus);
}
```
This hook takes the name of a valid `t-ref` directive, which should be present
in the template. It then checks whenever the component is mounted or patched if
the reference is not valid, and in this case, it will focus the node element.
This hook can be used like this:
```js
class SomeComponent extends Component {
static template = xml`
<div>
<input />
<input t-ref="myinput"/>
</div>`;
constructor(...args) {
super(...args);
useAutofocus("myinput");
}
}
```
## Reference
### One rule
There is only one rule: every hook for a component has to be called in the
constructor, in the _setup_ method, or in class fields:
```js ```js
// ok // ok
@@ -136,14 +42,6 @@ class SomeComponent extends Component {
state = useState({ value: 0 }); state = useState({ value: 0 });
} }
// also ok
class SomeComponent extends Component {
constructor(...args) {
super(...args);
this.state = useState({ value: 0 });
}
}
// also ok // also ok
class SomeComponent extends Component { class SomeComponent extends Component {
setup() { setup() {
@@ -159,15 +57,24 @@ class SomeComponent extends Component {
} }
``` ```
As you can see, the `useState` hook does not need to be given a reference to ## Lifecycle Hooks
the component. This is possible because there is a way to get a reference to the
current component: the `Component.current` static property is the reference to the
component instance that is currently being created.
Hooks need to be called in the constructor to ensure that this reference is All lifecycle hooks are documented in detail in their specific [section](component.md#lifecycle).
properly set. This is also a good thing for performance reasons (Owl can use
this to optimize its implementation), and for a clean architecture (this makes | Hook | Description |
it easier for developers to understand what is really happening in a component). | ----------------------------------------------------- | ---------------------------------------------------------------------- |
| **[onWillStart](component.md#willstart)** | async, before first rendering |
| **[onWillRender](component.md#willrender)** | just before component is rendered |
| **[onRendered](component.md#rendered)** | just after component is rendered |
| **[onMounted](component.md#mounted)** | just after component is rendered and added to the DOM |
| **[onWillUpdateProps](component.md#willupdateprops)** | async, before props update |
| **[onWillPatch](component.md#willpatch)** | just before the DOM is patched |
| **[onPatched](component.md#patched)** | just after the DOM is patched |
| **[onWillUnmount](component.md#willunmount)** | just before removing component from DOM |
| **[onWillDestroy](component.md#willdestroy)** | just before component is destroyed |
| **[onError](component.md#onerror)** | catch and handle errors (see [error handling page](error_handling.md)) |
## Other Hooks
### `useState` ### `useState`
@@ -178,9 +85,9 @@ The `useState` hook has to be given an object or an array, and will return
an observed version of it (using a `Proxy`). an observed version of it (using a `Proxy`).
```javascript ```javascript
const { useState } = owl.hooks; const { useState, Component } = owl;
class Counter extends owl.Component { class Counter extends Component {
static template = xml` static template = xml`
<button t-on-click="increment"> <button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>] Click Me! [<t t-esc="state.value"/>]
@@ -197,128 +104,38 @@ class Counter extends owl.Component {
It is important to remember that `useState` only works with objects or arrays. It It is important to remember that `useState` only works with objects or arrays. It
is necessary, since Owl needs to react to a change in state. is necessary, since Owl needs to react to a change in state.
### `onMounted`
`onMounted` is not a user hook, but is a building block designed to help make useful
abstractions. `onMounted` registers a callback, which will be called when the component
is mounted (see example on top of this page).
### `onWillUnmount`
`onWillUnmount` is not a user hook, but is a building block designed to help make useful
abstractions. `onWillUnmount` registers a callback, which will be called when the component
is unmounted (see example on top of this page).
### `onWillPatch`
`onWillPatch` is not a user hook, but is a building block designed to help make useful
abstractions. `onWillPatch` registers a callback, which will be called just
before the component patched.
### `onPatched`
`onPatched` is not a user hook, but is a building block designed to help make useful
abstractions. `onPatched` registers a callback, which will be called just
after the component patched.
### `onWillStart`
`onWillStart` is an asynchronous hook. This means that the function registered
in the hook will be run just before the component is first rendered and can return a
promise, to express the fact that it is an asynchronous operation.
Note that if there are more than one `onWillStart` registered callback, then they
will all be run in parallel.
It can be used to load some initial data. For example, the following hook will
automatically load some data from the server, and return an object that will
be ready whenever the component is rendered:
```js
function useLoader() {
const component = Component.current;
const record = useState({});
onWillStart(async () => {
const recordId = component.props.id;
Object.assign(record, await fetchSomeRecord(recordId));
});
return record;
}
```
Note that this example does not update the record value whenever props are
updated. For that situation, we need to use the `onWillUpdateProps` hook.
### `onWillUpdateProps`
Just like `onWillStart`, `onWillUpdateProps` is an asynchronous hook. It is
designed to be run whenever the component props are updated. This could be
useful to perform some asynchronous task such as fetching updated data.
```js
function useLoader() {
const component = Component.current;
const record = useState({});
async function updateRecord(id) {
Object.assign(record, await fetchSomeRecord(id));
}
onWillStart(() => updateRecord(component.props.id));
onWillUpdateProps((nextProps) => updateRecord(nextProps.id));
return record;
}
```
Note that if there are more than one `onWillUpdateProps` registered callback,
then they will all be run in parallel.
### `useContext`
See [`useContext`](context.md#usecontext) for reference documentation.
### `useRef` ### `useRef`
The `useRef` hook is useful when we need a way to interact with some inside part The `useRef` hook is useful when we need a way to interact with some inside part
of a component, rendered by Owl. It can work either on a DOM node, or on a component, of a component, rendered by Owl. It only work on a html element tagged by the
tagged by the `t-ref` directive: `t-ref` directive:
```xml ```xml
<div> <div>
<div t-ref="someDiv"/> <input t-ref="someDiv"/>
<SubComponent t-ref="someComponent"/> <span>hello</span>
</div> </div>
``` ```
In this example, the component will be able to access the `div` and the component In this example, the component will be able to access the `div` and the component
`SubComponent` using the `useRef` hook: `SubComponent` with the `useRef` hook:
```js ```js
class Parent extends Component { class Parent extends Component {
subRef = useRef("someComponent"); inputRef = useRef("someComponent");
divRef = useRef("someDiv");
someMethod() { someMethod() {
// here, if component is mounted, refs are active: // here, if component is mounted, refs are active:
// - this.divRef.el is the div HTMLElement // - this.inputRef.el is the input HTMLElement
// - this.subRef.comp is the instance of the sub component
// - this.subRef.el is the root HTML node of the sub component (i.e. this.subRef.comp.el)
} }
} }
``` ```
As shown by the example above, html elements are accessed by using the `el` As shown by the example above, the actual HTMLElement instance is accessed with
key, and components references are accessed with `comp`. the `el` key.
Notes:
- if used on a component, the reference will be set in the `refs`
variable between `willPatch` and `patched`,
- on a component, accessing `ref.el` will get the root node of the component.
The `t-ref` directive also accepts dynamic values with string interpolation The `t-ref` directive also accepts dynamic values with string interpolation
(like the [`t-attf-`](qweb_templating_language.md#dynamic-attributes) and (like the [`t-attf-`](templates.md#dynamic-attributes) and
`t-component` directives). For example, `t-component` directives). For example,
```xml ```xml
@@ -335,31 +152,41 @@ this.ref2 = useRef("component_2");
References are only guaranteed to be active while the parent component is mounted. References are only guaranteed to be active while the parent component is mounted.
If this is not the case, accessing `el` or `comp` on it will return `null`. If this is not the case, accessing `el` or `comp` on it will return `null`.
### `useSubEnv` ### `useSubEnv` and `useChildSubEnv`
The environment is sometimes useful to share some common information between The environment is sometimes useful to share some common information between
all components. But sometimes, we want to _scope_ that knowledge to a subtree. all components. But sometimes, we want to _scope_ that knowledge to a subtree.
For example, if we have a form view component, maybe we would like to make some For example, if we have a form view component, maybe we would like to make some
`model` object available to all sub components, but not to the whole application. `model` object available to all sub components, but not to the whole application.
This is where the `useSubEnv` hook may be useful: it lets a component add some This is where the `useChildSubEnv` hook may be useful: it lets a component add some
information to the environment in a way that only the component and its children information to the environment in a way that only its children
can access it: can access it:
```js ```js
class FormComponent extends Component { class FormComponent extends Component {
constructor(...args) { setup() {
super(...args);
const model = makeModel(); const model = makeModel();
// model will be available on this.env for this component and all children
useSubEnv({ model }); useSubEnv({ model });
// someKey will be available on this.env for all children
useChildSubEnv({ someKey: "value" });
} }
} }
``` ```
The `useSubEnv` takes one argument: an object which contains some key/value that The `useSubEnv` and `useChildSubEnv` hooks take one argument: an object which
will be added to the parent environment. Note that it will extend, not replace contains some key/value that will be added to the current environment. These hooks
the parent environment. And of course, the parent environment will not be will create a new env object with the new information:
affected.
- `useSubEnv` will assign this new `env` to itself and to all children components
- `useChildSubEnv` will only assign this new `env` to all children components.
As usual in Owl, [environments](environment.md) created with these two hooks are
frozen, to prevent unwanted modifications.
Note that both these hooks can be called an arbitrary number of times. The `env`
will then be updated accordingly.
### `useExternalListener` ### `useExternalListener`
@@ -372,65 +199,126 @@ to be closed:
useExternalListener(window, "click", this.closeMenu); useExternalListener(window, "click", this.closeMenu);
``` ```
### Making customized hooks ### `useComponent`
Hooks are a wonderful way to organize the code of a complex component by feature The `useComponent` hook is useful as a building block for some customized hooks,
instead of by lifecycle methods. They are like mixins, except that they can be that may need a reference to the component calling them.
easily composed together.
But, like every good things in life, hooks should be used with moderation. They are ```js
not the solution to every problem. function useSomething() {
const component = useComponent();
// now, component is bound to the instance of the current component
}
```
- they may be overkill: if your component needs to perform some action specific ### `useEnv`
to itself (so, the specific code does not need to be shared), there is nothing
wrong with a simple class method:
```js The `useEnv` hook is useful as a building block for some customized hooks,
// maybe overkill that may need a reference to the env of the component calling them.
class A extends Component {
constructor(...args) { ```js
super(...args); function useSomething() {
useMySpecificHook(); const env = useEnv();
} // now, env is bound to the env of the current component
}
```
### `useEffect`
This hook will run a callback when a component is mounted and patched, and
will run a cleanup function before patching and before unmounting the
the component (only if some dependencies have changed).
It has almost the same API as the React `useEffect` hook, except that the dependencies
are defined by a function instead of just the dependencies.
The `useEffect` hook takes two function: the effect function and the dependency
function. The effect function perform some task and return (optionally) a cleanup
function. The dependency function returns a list of dependencies. If any of these
dependencies changes, then the current effect will be cleaned up and reexecuted.
Here is an example without any dependencies:
```js
useEffect(
() => {
window.addEventListener("mousemove", someHandler);
return () => window.removeEventListener("mousemove", someHandler);
},
() => []
);
```
In the example above, the dependency list is empty, so the effect is only cleaned
up when the component is unmounted.
If the dependency function is skipped, then the effect will be cleaned up and
rerun at every patch.
Here is another example, of how one could implement a `useAutofocus` hook with
the `useEffect` hook:
```js
function useAutofocus(name) {
let ref = useRef(name);
useEffect(
(el) => el && el.focus(),
() => [ref.el]
);
}
```
This hook takes the name of a valid `t-ref` directive, which should be present
in the template. It then checks whenever the component is mounted or patched if
the reference is not valid, and in this case, it will focus the node element.
This hook can be used like this:
```js
class SomeComponent extends Component {
static template = xml`
<div>
<input />
<input t-ref="myinput"/>
</div>`;
setup() {
useAutofocus("myinput");
} }
}
```
// ok ## Example: mouse position
class B extends Component {
constructor(...args) { Here is the classical example of a non trivial hook to track the mouse position.
super(...args);
this.performSpecificTask(); ```js
} const { useState, onWillDestroy, Component } = owl;
// We define here a custom behaviour: this hook tracks the state of the mouse
// position
function useMouse() {
const position = useState({ x: 0, y: 0 });
function update(e) {
position.x = e.clientX;
position.y = e.clientY;
} }
``` window.addEventListener("mousemove", update);
onWillDestroy(() => {
window.removeEventListener("mousemove", update);
});
Note that the second solution is easier to extend in sub components. return position;
}
- they may be harder to test: if a customized hook injects some external side // Main root component
effect dependency, then it is harder to test without doing some non obvious class Root extends Component {
manipulation. For example, assume that we want to give a reference to a static template = xml`<div>Mouse: <t t-esc="mouse.x"/>, <t t-esc="mouse.y"/></div>`;
router in a `useRouter` hook. We could do this:
```js // this hooks is bound to the 'mouse' property.
const router = new Router(...); mouse = useMouse();
}
```
function useRouter() { Note that we use the prefix `use` for hooks, just like in React. This is just
return router; a convention.
}
```
As you can see, this does not _hook_ into the internal of the component. It
simply returns a global object, which is difficult to mock.
A better way would be to do something like this: get the reference from the
environment.
```js
function useRouter() {
const env = useEnv();
return env.router;
}
```
This means that we give control to the application developer to create the
router, which is good, so they can set it up, subclass it, ... And then, to
test our components, we can just add a mock router in the environment.
+92
View File
@@ -0,0 +1,92 @@
# 🦉 Form Input Bindings 🦉
It is very common to need to be able to read the value out of an html `input` (or
`textarea`, or `select`) in order to use it (note: it does not need to be in a
form!). A possible way to do this is to do it by hand:
```js
class Form extends owl.Component {
state = useState({ text: "" });
_updateInputValue(event) {
this.state.text = event.target.value;
}
}
```
```xml
<div>
<input t-on-input="_updateInputValue" />
<span t-esc="state.text" />
</div>
```
This works. However, this requires a little bit of _plumbing_ code. Also, the
plumbing code is slightly different if you need to interact with a checkbox,
or with radio buttons, or with select tags.
To help with this situation, Owl has a builtin directive `t-model`: its value
should be an observed value in the component (usually `state.someValue`). With
the `t-model` directive, we can write a shorter code, equivalent to the previous
example:
```js
class Form extends owl.Component {
state = { text: "" };
}
```
```xml
<div>
<input t-model="state.text" />
<span t-esc="state.text" />
</div>
```
The `t-model` directive works with `<input>`, `<input type="checkbox">`,
`<input type="radio">`, `<textarea>` and `<select>`:
```xml
<div>
<div>Text in an input: <input t-model="state.someVal"/></div>
<div>Textarea: <textarea t-model="state.otherVal"/></div>
<div>Boolean value: <input type="checkbox" t-model="state.someFlag"/></div>
<div>Selection:
<select t-model="state.color">
<option value="">Select a color</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
</div>
<div>
Selection with radio buttons:
<span>
<input type="radio" name="color" id="red" value="red" t-model="state.color"/>
<label for="red">Red</label>
</span>
<span>
<input type="radio" name="color" id="blue" value="blue" t-model="state.color" />
<label for="blue">Blue</label>
</span>
</div>
</div>
```
Like event handling, the `t-model` directive accepts the following modifiers:
| Modifier | Description |
| --------- | -------------------------------------------------------------------- |
| `.lazy` | update the value on the `change` event (default is on `input` event) |
| `.number` | try to parse the value to a number (using `parseFloat`) |
| `.trim` | trim the resulting value |
For example:
```xml
<input t-model.lazy="state.someVal" />
```
These modifiers can be combined. For instance, `t-model.lazy.number` will only
update a number whenever the change is done.
Note: the online playground has an example to show how it works.
-133
View File
@@ -1,133 +0,0 @@
# 🦉 Miscellaneous 🦉
## Content
- [Portal](#portal)
- [AsyncRoot](#asyncroot)
## `Portal`
### Overview
The component `Portal` is meant to be used as a transparent way to 'teleport' a piece
of DOM to the node represented by its sole `target` props.
This component aims at helping the implementation of the needed infrastructure
for modals (as in `bootstrap-modal`).
### Usage
The content it will teleport is defined within the `<Portal>` node and
internally uses the `default` [Slot](slots.md).
This slot must contain only **one** node, which in turn can have as many children as necessary.
The element under which the content will be teleported is represented as a selector
by the `target` props which only accepts a string as value.
The `target` props only supports static selector, and is not meant to be passed to `Portal`
as a variable. Namely, `<Portal target="'body'" />` is the intended use.
By contrast, `<Portal target="state.target" />` is not supported.
The component `Portal` has no particular state, rather it is meant to be a slave to its parent,
and ultimately just a way for the parent to teleport a piece of its own DOM elsewhere.
The `Portal`'s root node is always `<portal/>` and is placed where the teleported content
_would have_ been. It is this element that the [teleported events](#expected-behaviors) are re-directed on.
### Example
The canonic use-case is to implement a Dialog, where a Component may choose to break the natural
workflow to help the user put in some data, which it could use later on.
JavaScript:
```js
const { Component, mount } = owl;
const { Portal } = owl.misc;
class TeleportedComponent extends Component {}
class App extends Component {
static components = { Portal, TeleportedComponent };
}
mount(App, { target: document.body });
```
XML:
```xml
<templates>
<div t-name="TeleportedComponent">
<span>I will move soon enough</span>
</div>
<div t-name="App">
<span>I am like the rest of us</span>
<Portal target="'body'">
<TeleportedComponent />
</Portal>
</div>
</templates>
```
In this example, the `Portal` component will teleport the `TeleportedComponent`'s `div` as a child of the `body`.
`TeleportedComponent` is acting as a Dialog here.
The resulting DOM will look like:
```xml
<body>
<div>
<span>I am like the rest of us</span>
<portal></portal>
</div>
<div>
<span>I will move soon enough</span>
</div>
</body>
```
### Expected Behaviors
The teleported piece is updated as any other `Component`'s DOM and in the same sequence.
Namely the teleported piece will be updated in function of its parents components, and patched as
a normal child.
The [_business_ events](event_handling.md#business-dom-events) triggered by a child component will be stopped
to not bubble outside of the `target`. They will, on the other hand, be re-directed onto the
`Portal`'s root node and bubble up the DOM as if it were triggered by a regular child component.
Beware that those re-directed events are copies of the original event.
They have:
- The same payload.
- The same `originalComponent` than their original counterpart,
that is the actual Component that triggered it.
- A **different** `target` property than their original counterpart.
The `target` of a re-directed event is necessarily the `Portal`'s root node.
Pure DOM events do not follow this pattern and are free to bubble their natural, unaltered way
up to the `body`.
## `AsyncRoot`
When this component is used, a new rendering sub tree is created, such that the
rendering of that component (and its children) is not tied to the rendering of
the rest of the interface. It can be used on an asynchronous component, to
prevent it from delaying the rendering of the whole interface, or on a
synchronous one, such that its rendering isn't delayed by other (asynchronous)
components. Note that this directive has no effect on the first rendering, but
only on subsequent ones (triggered by state or props changes).
```xml
<div t-name="ParentComponent">
<SyncChild />
<AsyncRoot>
<AsyncChild/>
</AsyncRoot>
</div>
```
The `AsyncRoot` assumes that there is exactly one root node inside it. It can
be a dom node or a component.
-60
View File
@@ -1,60 +0,0 @@
# 🦉 Mounting an application 🦉
## Content
- [Overview](#overview)
- [API](#api)
## Overview
Mounting an Owl application is done by using the `mount` method (available in
`owl.mount` if you are using the iife build, or it can be directly imported
from `owl` if you are using a module system):
```js
const mount = { owl }; // if owl is available as an object
const env = { ... };
const app = await mount(MyComponent, { target: document.body, env });
```
Another example:
```js
const config = {
env: ...,
props: ...,
target: document.body,
position: "self",
};
const app = await mount(App, config);
```
A common way to initialize an application is to first setup an environment,
then to call the `mount` method.
## API
Mount takes two parameters:
- `C`, which should be a component class (NOT instance),
- `params`, which is an object with the following keys:
- `target (HTMLElement | DocumentFragment)`: the target of the mount operation
- `env (optional, Env)` an environment
- `position (optional, "first-child" | "last-child" | "self")` the position
where it should be mounted (see below for more informations)
- `props (optional, any)`: some initial values that are given as props. Useful
when the root component is configurable, or when testing sub components
Here are the various positions supported by Owl:
- `first-child`: with this option, the component will be prepended inside the target,
- `last-child` (default value): with this option, the component will be
appended in the target element,
- `self`: the target will be used as the root element for the component. This
means that the target has to be an HTMLElement (and not a document fragment).
In this situation, it is possible that the component cannot be unmounted. For
example, if its target is `document.body`.
The `mount` method returns a promise that resolves to the instance of the created
component.
-52
View File
@@ -1,52 +0,0 @@
# 🦉 Observer 🦉
Owl needs to be able to react to state changes. For example, whenever the state
of a component is changed, Owl needs to rerender it. To help with that, there is
an Observer class. Its job is to observe the state of an object (or array), and
to react to any change. The observer is implemented with the native `Proxy`
object. Note that this means that it will not work on older browsers.
Note that the `Observer` is used by the `useState` and `useContext` hooks. This
is the way most Owl applications will create observers. For the majority of
use cases, there is no need to directly instantiate an observer.
## Example
For example, this code will display `update` in the console:
```javascript
const observer = new owl.core.Observer();
observer.notifyCB = () => console.log("update");
const obj = observer.observe({ a: { b: 1 } });
obj.a.b = 2;
```
This example shows that an observer can observe nested properties.
## Reference
**observe** An observer can observe multiple values with the `observe` method.
This method takes an object or an array as its argument and will return a proxy
(which is mapped to the initial object/array). With this proxy, the observer
can detect whenever any internal value is changed.
**Registering a callback** Whenever an observer sees a state change, it will
call its `notifyCB` method. No additional information is given to the callback.
**deepRevNumber** Each observed value has an internal revision number, which
is incremented every time the value is observed. Sometimes, it can be useful
to obtain that number:
```js
const observer = new owl.core.Observer();
const obj = observer.observe({ a: { b: 1 } });
observer.revNumber(obj.a); // 1
obj.a.b = 2;
observer.revNumber(obj.a); // 2
```
The `revNumber` can also return 0, which indicates that the value is not
observed.
+17
View File
@@ -0,0 +1,17 @@
# 🦉 Portal 🦉
It is sometimes useful to be able to render some content outside the boundaries
of a component. To do that, Owl provides a special directive: `t-portal`:
```js
class SomeComponent extends Component {
static template = xml`
<div>this is inside the component</div>
<div t-portal="'body'">and this is outside</div>
`;
}
```
The `t-portal` directive takes a valid css selector as argument. The content of
the portalled template will be mounted at the corresponding location. Note that
Owl need to insert an empty text node at the location of the portalled content.
+191 -22
View File
@@ -4,8 +4,11 @@
- [Overview](#overview) - [Overview](#overview)
- [Definition](#definition) - [Definition](#definition)
- [Good Practices](#good-practices) - [Binding function props](#binding-function-props)
- [Dynamic Props](#dynamic-props) - [Dynamic Props](#dynamic-props)
- [Default Props](#default-props)
- [Props validation](#props-validation)
- [Good Practices](#good-practices)
## Overview ## Overview
@@ -38,8 +41,6 @@ The `props` object is made of every attributes defined on the template, with the
following exceptions: following exceptions:
- every attribute starting with `t-` are not props (they are QWeb directives), - every attribute starting with `t-` are not props (they are QWeb directives),
- `style` and `class` attributes are excluded as well (they are applied by Owl on
the root element of the component).
In the following example: In the following example:
@@ -47,7 +48,6 @@ In the following example:
<div> <div>
<ComponentA a="state.a" b="'string'"/> <ComponentA a="state.a" b="'string'"/>
<ComponentB t-if="state.flag" model="model"/> <ComponentB t-if="state.flag" model="model"/>
<ComponentC style="color:red;" class="left-pane" />
</div> </div>
``` ```
@@ -55,7 +55,193 @@ the `props` object contains the following keys:
- for `ComponentA`: `a` and `b`, - for `ComponentA`: `a` and `b`,
- for `ComponentB`: `model`, - for `ComponentB`: `model`,
- for `ComponentC`: empty object
## Binding function props
It is common to have the need to pass a callback as a prop. Since Owl components
are class based, the callback frequently needs to be bound to its owner component.
So, one can do this:
```js
class SomeComponent extends Component {
static template = xml`
<div>
<Child callback="doSomething"/>
</div>`;
setup() {
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
// ...
}
}
```
However, this is such a common use case that Owl provides a special suffix to do
just that: `.bind`. This looks like this:
```js
class SomeComponent extends Component {
static template = xml`
<div>
<Child callback.bind="doSomething"/>
</div>`;
doSomething() {
// ...
}
}
```
## Dynamic Props
The `t-props` directive can be used to specify totally dynamic props:
```xml
<div t-name="ParentComponent">
<Child t-props="some.obj"/>
</div>
```
```js
class ParentComponent {
static components = { Child };
some = { obj: { a: 1, b: 2 } };
}
```
## Default Props
If the static `defaultProps` property is defined, it will be used to complete
props received by the parent, if missing.
```js
class Counter extends owl.Component {
static defaultProps = {
initialValue: 0,
};
...
}
```
In the example above, the `initialValue` props is now by default set to 0.
## Props Validation
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
- hard to tell how a component should be used, by looking at its code.
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.
A props type system solves both issues, by describing the types and shapes
of the props. Here is how it works in Owl:
- `props` key is a static key (so, different from `this.props` in a component instance)
- it is optional: it is ok for a component to not define a `props` key.
- props are validated whenever a component is created/updated
- props are only validated in `dev` mode (see [how to configure an app](app.md#configuration))
- if a key does not match the description, an error is thrown
- it validates keys defined in (static) `props`. Additional keys given by the
parent will cause an error (unless the special prop `*` is present).
- it is an object or a list of strings
- a list of strings is a simplified props definition, which only lists the name
of the props. Also, if the name ends with `?`, it is considered optional.
- all props are by default required, unless they are defined with `optional: true`
(in that case, it is only done if there is a value)
- valid types are: `Number, String, Boolean, Object, Array, Date, Function`, and all
constructor functions (so, if you have a `Person` class, it can be used as a type)
- arrays are homogeneous (all elements have the same type/shape)
For each key, a `prop` definition is either a boolean, a constructor, a list of constructors, or an object:
- a boolean: indicate that the props exists, and is mandatory.
- a constructor: this should describe the type, for example: `id: Number` describe
the props `id` as a number
- a list of constructors. In that case, this means that we allow more than one
type. For example, `id: [Number, String]` means that `id` can be either a string
or a number.
- an object. This makes it possible to have more expressive definition. The following sub keys are then allowed (but not mandatory):
- `type`: the main type of the prop being validated
- `element`: if the type was `Array`, then the `element` key describes the type of each element in the array. If it is not set, then we only validate the array, not its elements,
- `shape`: if the type was `Object`, then the `shape` key describes the interface of the object. If it is not set, then we only validate the object, not its elements,
- `validate`: this is a function which should return a boolean to determine if
the value is valid or not. Useful for custom validation logic.
- `optional`: if true, the prop is not mandatory
There is a special `*` prop that means that additional prop are allowed. This is
sometimes useful for generic components that will propagate some or all their
props to their child components.
Note that default values cannot be defined for a mandatory props. Doing so will
result in a prop validation error.
Examples:
```js
class ComponentA extends owl.Component {
static props = ['id', 'url'];
...
}
class ComponentB extends owl.Component {
static props = {
count: {type: Number},
messages: {
type: Array,
element: {type: Object, shape: {id: Boolean, text: String }
},
date: Date,
combinedVal: [Number, Boolean],
optionalProp: { type: Number, optional: true }
};
...
}
```
```js
// only the existence of those 3 keys is documented
static props = ['message', 'id', 'date'];
```
```js
// only the existence of those 3 keys is documented. any other key is allowed.
static props = ['message', 'id', 'date', '*'];
```
```js
// size is optional
static props = ['message', 'size?'];
```
```js
static props = {
messageIds: {type: Array, element: Number}, // list of number
otherArr: {type: Array}, // just array. no validation is made on sub elements
otherArr2: Array, // same as otherArr
someObj: {type: Object}, // just an object, no internal validation
someObj2: {
type: Object,
shape: {
id: Number,
name: {type: String, optional: true},
url: String
]}, // object, with keys id (number), name (string, optional) and url (string)
someFlag: Boolean, // a boolean, mandatory (even if `false`)
someVal: [Boolean, Date], // either a boolean or a date
otherValue: true, // indicates that it is a prop
kindofsmallnumber: {
type: Number,
validate: n => (0 <= n && n <= 10)
},
size: {
validate: e => ["small", "medium", "large"].includes(e)
},
};
```
## Good Practices ## Good Practices
@@ -78,20 +264,3 @@ sent to the parent (for example, with an event).
Any value can go in a props. Strings, objects, classes, or even callbacks could Any value can go in a props. Strings, objects, classes, or even callbacks could
be given to a child component (but then, in the case of callbacks, communicating be given to a child component (but then, in the case of callbacks, communicating
with events seems more appropriate). with events seems more appropriate).
## Dynamic Props
The `t-props` directive can be used to specify totally dynamic props:
```xml
<div t-name="ParentComponent">
<Child t-props="some.obj"/>
</div>
```
```js
class ParentComponent {
static components = { Child };
some = { obj: { a: 1, b: 2 } };
}
```
-103
View File
@@ -1,103 +0,0 @@
# 🦉 Props Validation 🦉
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
- hard to tell how a component should be used, by looking at its code.
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.
A props type system solves both issues, by describing the types and shapes
of the props. Here is how it works in Owl:
- `props` key is a static key (so, different from `this.props` in a component instance)
- it is optional: it is ok for a component to not define a `props` key.
- props are validated whenever a component is created/updated
- props are only validated in `dev` mode (see [config page](config.md#mode))
- if a key does not match the description, an error is thrown
- it validates keys defined in (static) `props`. Additional keys given by the
parent will cause an error.
For example:
```js
class ComponentA extends owl.Component {
static props = ['id', 'url'];
...
}
class ComponentB extends owl.Component {
static props = {
count: {type: Number},
messages: {
type: Array,
element: {type: Object, shape: {id: Boolean, text: String }
},
date: Date,
combinedVal: [Number, Boolean]
};
...
}
```
- it is an object or a list of strings
- a list of strings is a simplified props definition, which only lists the name
of the props. Also, if the name ends with `?`, it is considered optional.
- all props are by default required, unless they are defined with `optional: true`
(in that case, validation is only done if there is a value)
- valid types are: `Number, String, Boolean, Object, Array, Date, Function`, and all
constructor functions (so, if you have a `Person` class, it can be used as a type)
- arrays are homogeneous (all elements have the same type/shape)
For each key, a `prop` definition is either a boolean, a constructor, a list of constructors, or an object:
- a boolean: indicate that the props exists, and is mandatory.
- a constructor: this should describe the type, for example: `id: Number` describe
the props `id` as a number
- a list of constructors. In that case, this means that we allow more than one
type. For example, `id: [Number, String]` means that `id` can be either a string
or a number.
- an object. This makes it possible to have more expressive definition. The following sub keys are then allowed (but not mandatory):
- `type`: the main type of the prop being validated
- `element`: if the type was `Array`, then the `element` key describes the type of each element in the array. If it is not set, then we only validate the array, not its elements,
- `shape`: if the type was `Object`, then the `shape` key describes the interface of the object. If it is not set, then we only validate the object, not its elements,
- `validate`: this is a function which should return a boolean to determine if
the value is valid or not. Useful for custom validation logic.
Examples:
```js
// only the existence of those 3 keys is documented
static props = ['message', 'id', 'date'];
```
```js
// size is optional
static props = ['message', 'size?'];
```
```js
static props = {
messageIds: {type: Array, element: Number}, // list of number
otherArr: {type: Array}, // just array. no validation is made on sub elements
otherArr2: Array, // same as otherArr
someObj: {type: Object}, // just an object, no internal validation
someObj2: {
type: Object,
shape: {
id: Number,
name: {type: String, optional: true},
url: String
]}, // object, with keys id (number), name (string, optional) and url (string)
someFlag: Boolean, // a boolean, mandatory (even if `false`)
someVal: [Boolean, Date], // either a boolean or a date
otherValue: true, // indicates that it is a prop
kindofsmallnumber: {
type: Number,
validate: n => (0 <= n && n <= 10)
},
size: {
validate: e => ["small", "medium", "large"].includes(e)
},
};
```
-153
View File
@@ -1,153 +0,0 @@
# 🦉 QWeb Engine 🦉
## Content
- [Overview](#overview)
- [Reference](#reference)
## Overview
[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary
templating engine used by Odoo. The QWeb class in the OWL project is an
implementation of that specification with a few interesting points:
- it compiles templates into functions that output a virtual DOM instead of a
string. This is necessary for the component system.
- it has a few extra directives: `t-component`, `t-on`, ...
We present in this section the engine, not the templating language.
## Reference
This section is about the javascript code that implements the `QWeb` specification.
Owl exports a `QWeb` class in `owl.QWeb`. To use it, it just needs to be
instantiated:
```js
const qweb = new owl.QWeb();
```
Its API is quite simple:
- **`constructor(config)`**: constructor. Takes an optional configuration object
with an optional `templates` string to add initial
templates (see `addTemplates` for more information on format of the string)
and an optional `translateFn` translate function (see the section on
[translations](#translations)).
```js
const qweb = new owl.QWeb({ templates: TEMPLATES, translateFn: _t });
```
- **`addTemplate(name, xmlStr, allowDuplicate)`**: add a specific template.
```js
qweb.addTemplate("mytemplate", "<div>hello</div>");
```
If the optional `allowDuplicate` is set to `true`, then `QWeb` will simply
ignore templates added for a second time. Otherwise, `QWeb` will crash.
- **`addTemplates(xmlStr)`**: add a list of templates (identified by `t-name`
attribute).
```js
const TEMPLATES = `
<templates>
<div t-name="App" class="main">main</div>
<div t-name="OtherComponent">other component</div>
</templates>`;
qweb.addTemplates(TEMPLATES);
```
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
which is a virtual representation of the DOM (see [vdom doc](../miscellaneous/vdom.md)).
```js
const vnode = qweb.render("App", component);
```
- **`renderToString(name, context)`**: renders a template, but returns an html
string.
```js
const str = qweb.renderToString("someTemplate", somecontext);
```
- **`registerTemplate(name, template)`**: static function to register a global
QWeb template. This is useful for commonly used components accross the
application, and for making a template available to an application without
having a reference to the actual QWeb instance.
```js
QWeb.registerTemplate("mytemplate", `<div>some template</div>`);
```
- **`registerComponent(name, Component)`**: static function to register an OWL Component
to QWeb's global registry. Globally registered Components can be used in
templates (see the `t-component` directive). This is useful for commonly used
components accross the application.
```js
class Dialog extends owl.Component { ... }
QWeb.registerComponent("Dialog", Dialog);
...
class ParentComponent extends owl.Component { ... }
qweb.addTemplate("ParentComponent", "<div><Dialog/></div>");
```
In some way, a `QWeb` instance is the core of an Owl application. It is the only
mandatory element of an [environment](environment.md). As such, it
has an extra responsibility: it can act as an event bus for internal communication
between Owl classes. This is the reason why `QWeb` actually extends [EventBus](event_bus.md).
### Translations
take care of this and "cherry-pick" 8464a1b04e7469434f9dcb3d68a543f58cb61b8e
If properly setup, Owl QWeb engine can translate all rendered templates. To do
so, it needs a translate function, which takes a string and returns a string.
For example:
```js
const translations = {
hello: "bonjour",
yes: "oui",
no: "non",
};
const translateFn = (str) => translations[str] || str;
const qweb = new QWeb({ translateFn });
```
Once setup, all rendered templates will be translated using `translateFn`:
- each text node will be replaced with its translation,
- each of the following attribute values will be translated as well: `title`,
`placeholder`, `label` and `alt`,
- translating text nodes can be disabled with the special attribute `t-translation`,
if its value is `off`.
So, with the above `translateFn`, the following templates:
```xml
<div>hello</div>
<div t-translation="off">hello</div>
<div>Are you sure?</div>
<input placeholder="hello" other="yes"/>
```
will be rendered as:
```xml
<div>bonjour</div>
<div>hello</div>
<div>Are you sure?</div>
<input placeholder="bonjour" other="yes"/>
```
Note that the translation is done during the compilation of the template, not
when it is rendered.
+131
View File
@@ -0,0 +1,131 @@
# 🦉 Reactivity 🦉
## Content
- [Overview](#overview)
- [`useState`](#usestate)
- [`reactive`](#reactive)
- [`markRaw`](#markraw)
- [`toRaw`](#toraw)
## Overview
Reactivity is a big topic in javascript frameworks. The goal is to provide a
simple way to manipulate state, in such a way that the interface automatically
update accordingly to state changes. Also, we obviously want this to happen in
a performant way.
To solve this issue, Owl provides two reactivity primitives:
- `reactive`, which returns a proxy to its first argument, and tracks all read/update
operation going through it,
- `useState`: a hook, that internally uses `reactive`, and is linked to its
owner component: any read operation will be tracked (key by key), and any
updates to these tracked values will cause the component to be rerendered.
Most of the time, the `useState` hook is the best solution.
Since version 2.0, Owl applies the fine grained reactivity at the component
level: props are automatically turned into reactive object, so Owl can track
which part of these props are consumed by each component, and is therefore able
to only rerender the impacted components.
## `useState`
Let us start by an example of how `useState` could be used:
```js
class Counter extends Component {
static template = xml`
<div t-on-click="increment">
<t t-esc="state.value"/>
</div>`;
setup() {
this.state = useState({ value: 0 });
}
increment() {
this.state.value++;
}
}
```
If one were to use a simple state object, Owl would not be aware that the value
was changed and that the component should be rerendered. With the `useState`
hook, `this.state` is now a reactive object, so this component works as expected.
## `reactive`
The `reactive` function is the basic reactivity primitive. It takes an object
or an array as first argument, and optionally, a function as the second argument.
The function will be called whenever any tracked value is updated.
```js
const obj = reactive({ a: 1 }, () => console.log("changed"));
obj.a = 2; // does not log anything: the 'a' key was not read
console.log(obj.a); // log 2, and reads the 'a' key => it is now tracked
obj.a = 3; // log 'changed' because we updated a tracked value
```
An important property of reactive objects is that they can be reobserved: this
will create an independant proxy that tracks another set of keys:
```js
const obj1 = reactive({ a: 1, b: 2 }, () => console.log("observer 1"));
const obj2 = reactive(obj1, () => console.log("observer 2"));
console.log(obj1.a); // log 1, and reads the 'a' key => it is now tracked by observer 1
console.log(obj1.b); // log 2, and 'b' is now tracked by observer 1
console.log(obj2.b); // log 2, and 'b' is now tracked by observer 1
obj2.a = 3; // log 'observer1', because observer2 does not track a
obj2.b = 3; // log 'observer1' and 'observer2'
```
Obviously, one can use `reactive` on the result of a `useState` if wanted, this
is the proper way to watch for some state changes.
## `markRaw`
Marks an object so that it is ignored by the reactivity system. This function returns its argument.
```js
const someObject = markRaw(...);
const state = useState({
a: 1,
obj: someObject
});
// here, state.obj === someObject
```
This is useful in some rare cases. For example, some complex and large object such
that going through the reactivity system may cause a non trivial performance slowdown.
However, use this function with caution: this is an escape hatch from the reactivity
system, and as such, using it may cause subtle and unintended issues!
## `toRaw`
Given a reactive object, this function returns the underlying, non-reactive,
corresponding object.
```js
// in setup
const state = useState({ value: 1 });
// later:
const rawState = toRaw(this.state);
rawState.value = 3; // will NOT be picked up by the reactivity system!!!
```
Here again, this is useful in some situations where we want to explicitely bypass
Owl, but using this function means that the responsability of coordinating
state update is given to the user code, instead of Owl. Subtle bugs may arise!
Also, normal (non-reactive objects) will be directly returned by `toRaw`:
```js
const obj = { a: 1 };
console.log(toRaw(obj) === obj); // true
```
+37
View File
@@ -0,0 +1,37 @@
# 🦉 References 🦉
The `useRef` hook is useful when we need a way to interact with some inside part
of a component, rendered by Owl. It can work either on a DOM node, or on a component,
targeted by the `t-ref` directive. See the [hooks section](hooks.md#useref) for
more detail.
As a short example, here is how we could set the focus on a given input:
```xml
<div>
<input t-ref="input"/>
<button t-on-click="focusInput">Click</button>
</div>
```
```js
import { useRef } from "owl/hooks";
class SomeComponent extends Component {
inputRef = useRef("input");
focusInput() {
this.inputRef.el.focus();
}
}
```
Be aware that the `el` property will only be set when the target of the `t-ref`
directive is mounted in the DOM. Otherwise, it will be set to `null`.
The `useRef` hook cannot be used to get a reference to an instance of a sub
component.
Note that this example uses the suffix `ref` to name the reference. This
is not mandatory, but it is a useful convention, so we do not forget that it is
a reference object.
+197 -54
View File
@@ -3,71 +3,92 @@
## Content ## Content
- [Overview](#overview) - [Overview](#overview)
- [Example](#example) - [Named slots](#named-slots)
- [Reference](#reference) - [Rendering Context](#rendering-context)
- [Default Slot](#default-slot)
- [Default Content](#default-content)
- [Dynamic slots](#dynamic-slots)
- [Slots and props](#slots-and-props)
- [Slot params](#slot-params)
- [Slot scopes](#slot-scopes)
## Overview ## Overview
Owl is a template based component system. There is therefore a need to be able Owl is a template based component system. There is therefore a need to be able
to make generic components. For example, imagine a generic `Dialog` to make generic components. For example, imagine a generic `Navbar`
component, which is able to display some arbitrary content. component, which displays a navbar, but with some customizable content. Since
the specific content is only known to the user of the `Navbar`, it would be nice
Obviously, we want to use this component everywhere in our application, to to specify it in the template where `Navbar` is used:
display various different content. The `Dialog` component is technically the
owner of its content, but is only a container. The user of the `Dialog` is
the component that want to _inject_ something inside the `Dialog`. This is
exactly what slots are for.
## Example
To make generic components, it is useful to be able for a parent component to _inject_
some sub template, but still be the owner. For example, a generic dialog component
will need to render some content, some footer, but with the parent as the
rendering context.
Slots are inserted with the `t-slot` directive:
```xml ```xml
<div t-name="Dialog" class="modal"> <div>
<div class="modal-title"><t t-esc="props.title"/></div> <Navbar>
<div class="modal-content"> <span>Hello Owl</span>
</Navbar>
</div>
```
This is exactly the way slots work! In the example above, the user of the `Navbar`
component specify some content (here, in the default slot). The `Navbar`
component can insert that content in its own template at the appropriate location.
An important information to notice is that the content of the slot is rendered in
the parent context, not in the navbar. As such, it can access values and methods
from the parent component.
Here is how the `Navbar` component could be defined, with the `t-slot` directive:
```xml
<div class="navbar">
<t t-slot="default"/>
<ul>
<!-- rest of the navbar here -->
</ul>
</div>
```
## Named slots
Default slots are very useful, but sometimes, we may need more than one slot.
This is what named slots are for! For example, suppose we implement a component
`InfoBox` that display a title and some specific content. Its template could look
like this:
```xml
<div class="info-box">
<div class="info-box-title">
<t t-slot="title"/>
<span class="info-box-close-button" t-on-click="close">X</span>
</div>
<div class="info-box-content">
<t t-slot="content"/> <t t-slot="content"/>
</div> </div>
<div class="modal-footer">
<t t-slot="footer"/>
</div>
</div> </div>
``` ```
Slots are defined by the caller, with the `t-set-slot` directive: And one could use it with the `t-set-slot` directive:
```xml ```xml
<div t-name="SomeComponent"> <InfoBox>
<div>some component</div> <t t-set-slot="title">
<Dialog title="'Some Dialog'"> Specific Title. It could be html also.
<t t-set-slot="content"> </t>
<div>hey</div> <t t-set-slot="content">
</t> <!-- some template here, with html, events, whatever -->
<t t-set-slot="footer"> </t>
<button t-on-click="doSomething">ok</button> </InfoBox>
</t>
</Dialog>
</div>
``` ```
In this example, the component `Dialog` will render the slots `content` and `footer` ## Rendering context
with its parent as rendering context. This means that clicking on the button
will execute the `doSomething` method on the parent, not on the dialog.
Note: Owl previously used the `t-set` directive to define the content of a slot. The content of the slots is actually rendered with the rendering context corresponding
This is deprecated and should no longer be used in new code. to where it was defined, not where it is positioned. This allows the user to define
event handlers that will be bound to the correct component (usually, the
grandparent of the slot content).
## Reference ## Default Slot
### Default Slot All elements inside the component which are not a named slot will be treated as
part of the content of the `default` slot. For example:
The first element inside the component which is not a named slot will
be considered the `default` slot. For example:
```xml ```xml
<div t-name="Parent"> <div t-name="Parent">
@@ -81,7 +102,20 @@ be considered the `default` slot. For example:
</div> </div>
``` ```
### Default content One can mix default slot and named slots:
```xml
<div>
<Child>
default content
<t t-set-slot="footer">
content for footer slot here
</t>
</Child>
</div>
```
## Default content
Slots can define a default content, in case the parent did not define them: Slots can define a default content, in case the parent did not define them:
@@ -96,12 +130,7 @@ Slots can define a default content, in case the parent did not define them:
<!-- will be rendered as: <div><span>default content</span></div> --> <!-- will be rendered as: <div><span>default content</span></div> -->
``` ```
Rendering context: the content of the slots is actually rendered with the ## Dynamic Slots
rendering context corresponding to where it was defined, not where it is
positioned. This allows the user to define event handlers that will be bound
to the correct component (usually, the grandparent of the slot content).
### Dynamic Slots
The `t-slot` directive is actually able to use any expressions, using string The `t-slot` directive is actually able to use any expressions, using string
interplolation: interplolation:
@@ -109,3 +138,117 @@ interplolation:
```xml ```xml
<t t-slot="{{current}}" /> <t t-slot="{{current}}" />
``` ```
This will evaluate the `current` expression, and insert the corresponding slot
at the place of the `t-slot` directive.
## Slots and props
In a sense, slots are almost the same as a prop: they define some information
to pass to the child component. To make it possible to use it, and to pass it
down to sub component, Owl actually define a special prop `slots` that contains
all slot information given to the component. It looks like this:
```js
{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }
```
So, a component can pass its slots to a subcomponent like this:
```xml
<Child slots="props.slots"/>
```
## Slot params
For advanced usecases, it may be necessary to pass additional information to a
slot. This can be done by providing extra key/value pairs to the `t-set-slot`
directive. Then, the generic component can read them in its prop `slots`.
For example, here is how a Notebook component could be implemented (a component
with multiple page, and a tab bar, which only render the current active page,
and each page has a title).
```js
class Notebook extends Component {
static template = xml`
<div class="notebook">
<div class="tabs">
<t t-foreach="tabNames" t-as="tab" t-key="tab_index">
<span t-att-class="{active:tab_index === activeTab}" t-on-click="() => state.activeTab=tab">
<t t-esc="props.slots[tab].title"/>
</span>
</t>
</div>
<div class="page">
<t t-slot="{{currentSlot}}"/>
</div>
</div>`;
setup() {
this.state = useState({ activeTab: 0 });
this.tabNames = Object.keys(this.props.slots);
}
get currentSlot() {
return this.tabNames[this.state.activeTab];
}
}
```
Notice how one can read the `title` value for each slots. Here is how one could
use this `Notebook` component:
```xml
<Notebook>
<t t-set-slot="page1" title="'Page 1'">
<div>this is in the page 1</div>
</t>
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
<div>this is in the page 2</div>
</t>
</Notebook>
```
Slot params works like normal props, so one can use the `.bind` suffix to
bind a function if needed.
## Slot scopes
For other kinds of advanced use cases, the content of a slot may depends on some
information specific to the generic component. This is the opposite of the slot
params.
To solve this kind of problems, one can use the `t-slot-scope` directive along
with the `t-set-slot`. This defines the name of a variable that can access
everything given by the child component:
```xml
<MyComponent>
<t t-set-slot="foo" t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</t>
</MyComponent>
```
And the child component that includes the slot can provide values like this:
```xml
<t t-slot="foo" bool="other_var" num="5">
```
In the case of the default slot, you may declare the slot scope directly on the
component itself:
```xml
<MyComponent t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</MyComponent>
```
Slot values works like normal props, so one can use the `.bind` suffix to
bind a function if needed.
-184
View File
@@ -1,184 +0,0 @@
# 🦉 Tags 🦉
## Content
- [Overview](#overview)
- [`xml` tag](#xml-tag)
- [`css` tag](#css-tag)
## Overview
Tags are very small helpers intended to make it easy to write inline templates
or styles. There are currently two tags: `css` and `xml`. With these functions,
it is possible to write [single file components](../learning/how_to_write_sfc.md).
## XML tag
The `xml` tag is certainly the most useful tag. It is used to define an inline
QWeb template for a component. Without tags, creating a standalone component
would look like this:
```js
import { Component } from 'owl'
const name = 'some-unique-name';
const template = `
<div>
<span t-if="somecondition">text</span>
<button t-on-click="someMethod">Click</button>
</div>
`;
QWeb.registerTemplate(name, template);
class MyComponent extends Component {
static template = name;
...
}
```
With tags, this process is slightly simplified. The name is uniquely generated,
and the template is automatically registered:
```js
const { Component } = owl;
const { xml } = owl.tags;
class MyComponent extends Component {
static template = xml`
<div>
<span t-if="somecondition">text</span>
<button t-on-click="someMethod">Click</button>
</div>
`;
...
}
```
## CSS tag
The CSS tag is useful to define a css stylesheet in the javascript file:
```js
class MyComponent extends Component {
static template = xml`
<div class="my-component">some template</div>
`;
static style = css`
.my-component {
color: red;
}
`;
}
```
The `css` tag registers internally the css information. Then, whenever the first
instance of the component is created, will add a `<style>` tag to the document
`<head>`.
Note that to make it more useful, like other css preprocessors, the `css` tag
accepts a small extension of the css specification: css scopes can be nested,
and the rules will then be expanded by the `css` helper:
```scss
.my-component {
display: block;
.sub-component h {
color: red;
}
}
```
will be formatted as:
```css
.my-component {
display: block;
}
.my-component .sub-component h {
color: red;
}
```
This extension brings another useful feature: the `&` selector which refers to
the parent selector. For example, we want our component to be red when hovered.
We would like to write something like:
```scss
.my-component {
display: block;
:hover {
color: red;
}
}
```
but it will be formatted as:
```css
.my-component {
display: block;
}
.my-component :hover {
color: red;
}
```
The `&` selector can be used to solve this problem:
```scss
.my-component {
display: block;
&:hover {
color: red;
}
}
```
will be formatted as:
```css
.my-component {
display: block;
}
.my-component:hover {
color: red;
}
```
Now, there is no additional processing done by the `css` tag. However, since it
is done in javascript at runtime, we actually have more power. For example:
1. sharing values between javascript and css:
```js
import { theme } from "./theme";
class MyComponent extends Component {
static template = xml`<div class="my-component">...</div>`;
static style = css`
.my-component {
color: ${theme.MAIN_COLOR};
background-color: ${theme.SECONDARY_color};
}
`;
}
```
2. scoping rules to the current component:
```js
import { generateUUID } from "./utils";
const uuid = generateUUID();
class MyComponent extends Component {
static template = xml`<div data-o-${uuid}="">...</div>`;
static style = css`
[data-o-${uuid}] {
color: red;
}
`;
}
```
@@ -1,12 +1,11 @@
# 🦉 QWeb Templating Language🦉 # 🦉 Templates 🦉
## Content ## Content
- [Overview](#overview) - [Overview](#overview)
- [Directives](#directives) - [Directives](#directives)
- [Reference](#reference) - [QWeb Template reference](#qweb-template-reference)
- [White Spaces](#white-spaces) - [White Spaces](#white-spaces)
- [Root Nodes](#root-nodes)
- [Expression Evaluation](#expression-evaluation) - [Expression Evaluation](#expression-evaluation)
- [Static html Nodes](#static-html-nodes) - [Static html Nodes](#static-html-nodes)
- [Outputting Data](#outputting-data) - [Outputting Data](#outputting-data)
@@ -16,17 +15,20 @@
- [Dynamic Class Attribute](#dynamic-class-attribute) - [Dynamic Class Attribute](#dynamic-class-attribute)
- [Dynamic Tag Names](#dynamic-tag-names) - [Dynamic Tag Names](#dynamic-tag-names)
- [Loops](#loops) - [Loops](#loops)
- [Rendering Sub Templates](#rendering-sub-templates) - [Sub Templates](#sub-templates)
- [Dynamic Sub Templates](#dynamic-sub-templates) - [Dynamic Sub Templates](#dynamic-sub-templates)
- [Translations](#translations)
- [Debugging](#debugging) - [Debugging](#debugging)
- [Fragments](#fragments)
- [Inline templates](#inline-templates)
- [Rendering svg](#rendering-svg)
- [Restrictions](#restrictions)
## Overview ## Overview
[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary Owl templates are describe using the [QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) specification. It is based on the XML format, and used
templating engine used by Odoo. It is based on the XML format, and used
mostly to generate HTML. In OWL, QWeb templates are compiled into functions that mostly to generate HTML. In OWL, QWeb templates are compiled into functions that
generate a virtual dom representation of the HTML. generate a virtual dom representation of the HTML. Also, since Owl is a live
component system, there are additional directives specific to Owl (such as `t-on`).
```xml ```xml
<div> <div>
@@ -53,34 +55,33 @@ extensions.
For reference, here is a list of all standard QWeb directives: For reference, here is a list of all standard QWeb directives:
| Name | Description | | Name | Description |
| ------------------------------ | -------------------------------------------------------------- | | ------------------------------ | --------------------------------------------------------------- |
| `t-esc` | [Outputting safely a value](#outputting-data) | | `t-esc` | [Outputting safely a value](#outputting-data) |
| `t-raw` | [Outputting value, without escaping](#outputting-data) | | `t-out` | [Outputting value, possibly without escaping](#outputting-data) |
| `t-set`, `t-value` | [Setting variables](#setting-variables) | | `t-set`, `t-value` | [Setting variables](#setting-variables) |
| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) | | `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) |
| `t-foreach`, `t-as` | [Loops](#loops) | | `t-foreach`, `t-as` | [Loops](#loops) |
| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) | | `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) |
| `t-call` | [Rendering sub templates](#rendering-sub-templates) | | `t-call` | [Rendering sub templates](#sub-templates) |
| `t-debug`, `t-log` | [Debugging](#debugging) | | `t-debug`, `t-log` | [Debugging](#debugging) |
| `t-translation` | [Disabling the translation of a node](#translations) | | `t-translation` | [Disabling the translation of a node](translations.md) |
| `t-name` | [Defining a template (not really a directive)](qweb_engine.md) |
The component system in Owl requires additional directives, to express various The component system in Owl requires additional directives, to express various
needs. Here is a list of all Owl specific directives: needs. Here is a list of all Owl specific directives:
| Name | Description | | Name | Description |
| ------------------------ | ------------------------------------------------------------------------------- | | -------------------------------------- | --------------------------------------------------------------- |
| `t-component`, `t-props` | [Defining a sub component](component.md#composition) | | `t-component`, `t-props` | [Defining a sub component](component.md#sub-components) |
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#references) | | `t-ref` | [Setting a reference to a dom node or a sub component](refs.md) |
| `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) | | `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) |
| `t-on-*` | [Event handling](event_handling.md) | | `t-on-*` | [Event handling](event_handling.md) |
| `t-transition` | [Defining an animation](animations.md#css-transitions) | | `t-portal` | [Portal](portal.md) |
| `t-slot` | [Rendering a slot](slots.md) | | `t-slot`, `t-set-slot`, `t-slot-scope` | [Rendering a slot](slots.md) |
| `t-model` | [Form input bindings](component.md#form-input-bindings) | | `t-model` | [Form input bindings](input_bindings.md) |
| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) | | `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) |
## Reference ## QWeb Template Reference
### White Spaces ### White Spaces
@@ -90,32 +91,6 @@ White spaces in a template are handled in a special way:
- if a whitespace-only text node contains a linebreak, it is ignored - if a whitespace-only text node contains a linebreak, it is ignored
- the previous rules do not apply if we are in a `<pre>` tag - the previous rules do not apply if we are in a `<pre>` tag
### Root Nodes
For many reasons, Owl QWeb templates should have a single root node. More
precisely, the result of a template rendering should have a single root node:
```xml
<!–– not ok: two root nodes ––>
<t>
<div>foo</div>
<div>bar</div>
</t>
<!–– ok: result has one single root node ––>
<t>
<div t-if="someCondition">foo</div>
<span t-else="">bar</span>
</t>
```
Extra root nodes will actually be ignored (even though they will be rendered
in memory).
Note: this does not apply to subtemplates (see the `t-call` directive). In that
case, they will be inlined in the main template, and can actually have many
root nodes.
### Expression Evaluation ### Expression Evaluation
QWeb expressions are strings that will be processed at compile time. Each variable in QWeb expressions are strings that will be processed at compile time. Each variable in
@@ -190,24 +165,29 @@ rendered with the value `value` set to `42` in the rendering context yields:
<p>42</p> <p>42</p>
``` ```
The `t-raw` directive is almost the same as `t-esc`, but without the escaping. The `t-out` directive is almost the same as `t-esc`, but possibly without the
This is mostly useful to inject a raw html string somewhere. Obviously, this escaping. The difference is that the value received by the `t-out` directive
is unsafe to do in general, and should only be used for strings known to be safe. will only be not-escaped if it has been marked as such, using the `markup`
utility function:
```xml For example, in the following component:
<p><t t-raw="value"/></p>
```js
const { markup, Component, xml } = owl;
class SomeComponent extends Component {
static template = xml`
<t t-out="value1"/>
<t t-out="value2"/>`;
value1 = "<div>some text 1</div>";
value2 = markup("<div>some text 2</div>");
}
``` ```
rendered with the value `value` set to `<span>foo</span>` in the rendering context yields: The first `t-out` will act as a `t-esc` directive, which means that the content
of `value1` will be escaped. However, since `value2` has been tagged as a markup,
```html this will be injected as html.
<p><span>foo</span></p>
```
Note that since the content of the expression is not known beforehand, the `t-raw`
directive has to parse the html (and convert it to a virtual dom structure) for
each rendering. So, it will be much slower than a regular template. It is
therefore advised to limit the use of `t-raw` whenever possible.
### Setting Variables ### Setting Variables
@@ -368,7 +348,7 @@ collection to iterate on, and a second parameter `t-as` providing the name to us
for the current item of the iteration: for the current item of the iteration:
```xml ```xml
<t t-foreach="[1, 2, 3]" t-as="i"> <t t-foreach="[1, 2, 3]" t-as="i" t-key="i">
<p><t t-esc="i"/></p> <p><t t-esc="i"/></p>
</t> </t>
``` ```
@@ -384,13 +364,17 @@ will be rendered as:
Like conditions, `t-foreach` applies to the element bearing the directives attribute, and Like conditions, `t-foreach` applies to the element bearing the directives attribute, and
```xml ```xml
<p t-foreach="[1, 2, 3]" t-as="i"> <p t-foreach="[1, 2, 3]" t-as="i" t-key="i">
<t t-esc="i"/> <t t-esc="i"/>
</p> </p>
``` ```
is equivalent to the previous example. is equivalent to the previous example.
An important difference should be made with the usual `QWeb` behaviour: Owl
requires the presence of a `t-key` directive, to be able to properly reconcile
renderings.
`t-foreach` can iterate on an array (the current item will be the current value) `t-foreach` can iterate on an array (the current item will be the current value)
or an object (the current item will be the current key). or an object (the current item will be the current key).
@@ -416,7 +400,7 @@ into the global context.
<t t-set="existing_variable" t-value="false"/> <t t-set="existing_variable" t-value="false"/>
<!-- existing_variable now False --> <!-- existing_variable now False -->
<p t-foreach="Array(3)" t-as="i"> <p t-foreach="Array(3)" t-as="i" t-key="i">
<t t-set="existing_variable" t-value="true"/> <t t-set="existing_variable" t-value="true"/>
<t t-set="new_variable" t-value="true"/> <t t-set="new_variable" t-value="true"/>
<!-- existing_variable and new_variable now true --> <!-- existing_variable and new_variable now true -->
@@ -430,17 +414,14 @@ Even though Owl tries to be as declarative as possible, the DOM does not fully
expose its state declaratively in the DOM tree. For example, the scrolling state, expose its state declaratively in the DOM tree. For example, the scrolling state,
the current user selection, the focused element or the state of an input are not the current user selection, the focused element or the state of an input are not
set as attribute in the DOM tree. This is why we use a virtual dom set as attribute in the DOM tree. This is why we use a virtual dom
algorithm to keep the actual DOM node as much as possible. algorithm to make sure we keep the actual DOM node instead of replacing it with
a new one.
However, in some situations, this is not enough, and we need to help Owl decide
if an element is actually the same, or is a different element with the same
properties.
Consider the following situation: we have a list of two items `[{text: "a"}, {text: "b"}]` Consider the following situation: we have a list of two items `[{text: "a"}, {text: "b"}]`
and we render them in this template: and we render them in this template:
```xml ```xml
<p t-foreach="items" t-as="item"><t t-esc="item.text"/></p> <p t-foreach="items" t-as="item" t-key="item_index"><t t-esc="item.text"/></p>
``` ```
The result will be two `<p>` tags with text `a` and `b`. Now, if we swap them, The result will be two `<p>` tags with text `a` and `b`. Now, if we swap them,
@@ -501,7 +482,7 @@ using the `...` javascript operator. For example:
The `...` operator will convert the `Set` (or any other iterables) into a list, The `...` operator will convert the `Set` (or any other iterables) into a list,
which will work with Owl QWeb. which will work with Owl QWeb.
### Rendering Sub Templates ### Sub Templates
QWeb templates can be used for top level rendering, but they can also be used QWeb templates can be used for top level rendering, but they can also be used
from within another template (to avoid duplication or give names to parts of from within another template (to avoid duplication or give names to parts of
@@ -574,21 +555,6 @@ using string interpolation. For example:
Here, the name of the template is obtained from the `template` value in the Here, the name of the template is obtained from the `template` value in the
template rendering context. template rendering context.
### Translations
By default, QWeb specify that templates should be translated. If this behaviour
is not wanted, there is a `t-translation` directive which can turn off
translations (if it is set to the `off` value), with the following rules:
- each text node will be replaced with its translation,
- each of the following attribute values will be translated as well: `title`,
`placeholder`, `label` and `alt`,
- translating text nodes can be disabled with the special attribute `t-translation`,
if its value is `off`.
See [here](qweb_engine.md#translations) for more information on how to setup a
translate function in Owl QWeb.
### Debugging ### Debugging
The javascript QWeb implementation provides two useful debugging directives: The javascript QWeb implementation provides two useful debugging directives:
@@ -611,3 +577,117 @@ will stop execution if the browser dev tools are open.
``` ```
will print 42 to the console. will print 42 to the console.
## Fragments
Owl 2 supports templates with an arbitrary number of root elements, or even just
a text node. So, the following templates are all valid:
```xml
hello owl. This is just a text node!
```
```xml
<div>hello</div>
```
```xml
<div>hello</div>
<div>ola</div>
```
```xml
<div t-if="someCondition"><SomeChildComponent/></div>
```
```xml
<t t-if="someCondition"><SomeChildComponent/></t>
```
## Inline templates
Most real applications will define their templates in a XML file, to benefit
from the XML ecosystem, and to do some additional processing, such as translating
them. However, in some cases, it is convenient to be able to define a template
inline. To do so, one can use the `xml` helper function:
```js
const { Component, xml } = owl;
class MyComponent extends Component {
static template = xml`
<div>
<span t-if="somecondition">text</span>
<button t-on-click="someMethod">Click</button>
</div>
`;
...
}
mount(MyComponent, document.body);
```
This function simply generates an unique string id, and register the template
under that id in the internals of Owl, then return the id.
## Rendering svg
Owl components can be used to generate dynamic SVG graphs:
```js
class Node extends Component {
static template = xml`
<g>
<circle t-att-cx="props.x" t-att-cy="props.y" r="4" fill="black"/>
<text t-att-x="props.x - 5" t-att-y="props.y + 18"><t t-esc="props.node.label"/></text>
<t t-set="childx" t-value="props.x + 100"/>
<t t-set="height" t-value="props.height/(props.node.children || []).length"/>
<t t-foreach="props.node.children || []" t-as="child">
<t t-set="childy" t-value="props.y + child_index*height"/>
<line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-att-y2="childy" stroke="black" />
<Node x="childx" y="childy" node="child" height="height"/>
</t>
</g>
`;
static components = { Node };
}
class RootNode extends Component {
static template = xml`
<svg height="180">
<Node node="graph" x="10" y="20" height="180"/>
</svg>
`;
static components = { Node };
graph = {
label: "a",
children: [
{ label: "b" },
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
{ label: "f", children: [{ label: "g" }] },
],
};
}
```
This `RootNode` component will then display a live SVG representation of the
graph described by the `graph` property. Note that there is a recursive structure
here: the `Node` component uses itself as a subcomponent.
**Important note:** Owl needs to properly set the namespace for each svg elements.
Since Owl compile each template separately, it is not able to determine easily
if a template is supposed to be included in a svg namespace or not. Therefore,
Owl depends on a heuristic: if a tag is either `svg`, `g` or `path`, then it will
be considered as svg. In practice, this means that each component or each sub
templates (included with `t-call`) should have one of these tag as root tag.
## Restrictions
Note that Owl templates forbid the use of tag and or attributes starting with
the `block-` string. This restriction prevents name collision with the internal
code of Owl.
```xml
<div><block-1>this will not be accepted by Owl</block-1></div>
```
+70
View File
@@ -0,0 +1,70 @@
# 🦉 Translations 🦉
If properly setup, Owl can translate all rendered templates. To do
so, it needs a translate function, which takes a string and returns a string.
For example:
```js
const translations = {
hello: "bonjour",
yes: "oui",
no: "non",
};
const translateFn = (str) => translations[str] || str;
const app = new App(Root, { templates, tranaslateFn });
// ...
```
See the [app configuration page](app.md#configuration) for more info on how to
configure an Owl application.
Once setup, all rendered templates will be translated using `translateFn`:
- each text node will be replaced with its translation,
- each of the following attribute values will be translated as well: `title`,
`placeholder`, `label` and `alt`,
- translating text nodes can be disabled with the special attribute `t-translation`,
if its value is `off`.
So, with the above `translateFn`, the following templates:
```xml
<div>hello</div>
<div t-translation="off">hello</div>
<div>Are you sure?</div>
<input placeholder="hello" other="yes"/>
```
will be rendered as:
```xml
<div>bonjour</div>
<div>hello</div>
<div>Are you sure?</div>
<input placeholder="bonjour" other="yes"/>
```
Note that the translation is done during the compilation of the template, not
when it is rendered.
In some case, it is useful to be able to extend the list of translatable attributes.
For example, one may want to also translate `data-title` attributes. To do that,
we can define additional attributes with the `translatableAttributes` option:
```js
const app = new App(Root, { templates, tranaslateFn, translatableAttributes: ["data-title"] });
// ...
```
It is also possible to remove an attribute from the default list by prefixing it with `-`:
```js
const app = new App(Root, {
templates,
tranaslateFn,
translatableAttributes: ["data-title", "-title"],
});
// data-title attribute will be translated, but not title attribute...
```
+17 -107
View File
@@ -6,11 +6,8 @@ functions are all available in the `owl.utils` namespace.
## Content ## Content
- [`whenReady`](#whenready): executing code when DOM is ready - [`whenReady`](#whenready): executing code when DOM is ready
- [`loadJS`](#loadjs): loading script files
- [`loadFile`](#loadfile): loading a file (useful for templates) - [`loadFile`](#loadfile): loading a file (useful for templates)
- [`escape`](#escape): sanitizing strings - [`EventBus`](#eventbus): a simple EventBus
- [`debounce`](#debounce): limiting rate of function calls
- [`shallowEqual`](#shallowequal): shallow object comparison
## `whenReady` ## `whenReady`
@@ -19,40 +16,20 @@ not ready yet, resolved directly otherwise). If called with a callback as
argument, it executes it as soon as the DOM ready (or directly). argument, it executes it as soon as the DOM ready (or directly).
```js ```js
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function ([templates]) { const { whenReady } = owl;
const qweb = new owl.QWeb({ templates });
const env = { qweb }; await whenReady();
await mount(App, { env, target: document.body }); // do something
});
``` ```
or alternatively: or alternatively:
```js ```js
owl.utils.whenReady(function () { whenReady(function () {
const qweb = new owl.QWeb(); // do something
const env = { qweb };
await mount(App, { env, target: document.body });
}); });
``` ```
## `loadJS`
`loadJS` takes a url (string) for a javascript resource, and loads it (by adding
a script tag in the document head). It returns a promise, so the caller can
properly reacts when it is ready. Also, it is smart: it maintains a list of urls
previously loaded (or currently being loaded), and prevent doing twice the work.
For example, it is useful for lazy loading external libraries:
```js
class MyComponent extends owl.Component {
willStart() {
return owl.utils.loadJS("/static/libs/someLib.js");
}
}
```
## `loadFile` ## `loadFile`
`loadFile` is a helper function to fetch a file. It simply `loadFile` is a helper function to fetch a file. It simply
@@ -60,89 +37,22 @@ performs a `GET` request and returns the resulting string in a promise. The
initial usecase for this function is to load a template file. For example: initial usecase for this function is to load a template file. For example:
```js ```js
const { loadFile } = owl;
async function makeEnv() { async function makeEnv() {
const templates = await owl.utils.loadFile("templates.xml"); const templates = await loadFile("templates.xml");
const qweb = new owl.QWeb({ templates }); // do something
return { qweb };
} }
``` ```
Note that unlike `loadJS`, this function returns the content of the file as a ## `EventBus`
string. It does not add a `script` tag or any other side effect.
## `escape` It is a simple `EventBus`, with the same API as usual DOM elements, and an
additional `trigger` method to dispatch events:
Sometimes, we need to display dynamic data (for example user-generated data) in
the user interface. If this is done by a `QWeb` template, it is not an issue:
```xml
<div><t t-esc="user.data"/></div>
```
The `QWeb` engine will create a `div` node and add the content of the `user.data`
string as a text node, so the web browser will not parse it as html. However,
it may be a problem if this is done with some javascript code like this:
```js ```js
class BadComponent extends Component { const bus = new EventBus();
// some template with a ref to a div bus.addEventListener("event", () => console.log("something happened"));
// some code ...
mounted() { bus.trigger("event"); // 'something happened' is logged
this.divRef.el.innerHTML = this.state.value;
}
}
```
In this case, the content of the `div` will be parsed as html, which may inject
unwanted behaviour. To fix this, the `escape` function will simply transform a
string into an escaped version of the same string, which will be properly displayed
by the browser, but which will not be parsed as html (for example, `"<ok>"` is
escaped to the string: `"&lt;ok&gt;"`). So, the bad example above can be fixed
with the following change:
```js
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
```
## `debounce`
The `debounce` function is useful when we want to limit the number of times some
function/action is perfomed. For example, this may be useful to prevent issue
with people double clicking on a button.
It takes three arguments:
- `func` (function): this is the function that will be rate limited
- `wait` (number): this is the number of milliseconds that we want to use to
rate limit the function `func`
- `immediate` (optional, boolean, default=false): if `immediate` is true, the
function will be triggered immediately (leading edge of the interval). If false,
the function will be triggered at the end (trailing edge).
It returns a function. For example:
```js
const debounce = owl.utils.debounce;
window.addEventListener("mousemove", debounce(doSomething, 100));
```
As this example shows, it is usualy useful for event handlers which are triggered
very quickly, such as `scroll` or `mousemove` events.
## `shallowEqual`
This function checks if two objects have the same values assigned to each keys:
```js
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 }); // false
```
However, for performance reasons, it assumes that the two objects have the same
keys. If we are in a situation where this is not guaranteed, the following code
will work:
```js
const completeShallowEqual = (a, b) => shallowEqual(a, b) && shallowEqual(b, a);
``` ```
+4 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "2.0.0-alpha1", "version": "2.0.0-beta.2",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js", "browser": "dist/owl.iife.js",
@@ -13,10 +13,10 @@
"node": ">=12.18.3" "node": ">=12.18.3"
}, },
"scripts": { "scripts": {
"build:bundle": "rollup -c", "build:bundle": "rollup -c --failAfterWarnings",
"build": "npm run build:bundle", "build": "npm run build:bundle",
"test": "jest", "test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"playground:serve": "python3 tools/server.py || python tools/server.py", "playground:serve": "python3 tools/server.py || python tools/server.py",
"playground": "npm run build && npm run playground:serve", "playground": "npm run build && npm run playground:serve",
@@ -46,6 +46,7 @@
"git-rev-sync": "^1.12.0", "git-rev-sync": "^1.12.0",
"github-api": "^3.3.0", "github-api": "^3.3.0",
"jest": "^27.1.0", "jest": "^27.1.0",
"jest-diff": "^27.3.1",
"jest-environment-jsdom": "^27.1.0", "jest-environment-jsdom": "^27.1.0",
"live-server": "^1.2.1", "live-server": "^1.2.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉 # 🦉 OWL Roadmap 🦉
- Current version: 1.4.7 - Current version: 1.4.10
- Status: stable - Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may This roadmap is only an attempt at predicting Owl's future. Everything may
+91 -48
View File
@@ -1,8 +1,10 @@
import { Component } from "../component/component"; import { Component, ComponentConstructor } from "../component/component";
import { ComponentNode } from "../component/component_node"; import { ComponentNode } from "../component/component_node";
import { MountOptions } from "../component/fibers"; import { MountOptions } from "../component/fibers";
import { Scheduler } from "../component/scheduler"; import { Scheduler } from "../component/scheduler";
import { TemplateSet } from "./template_set"; import { TemplateSet, TemplateSetConfig } from "./template_set";
import { nodeErrorHandlers } from "../component/error_handling";
import { validateTarget } from "../utils";
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f // reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
@@ -10,62 +12,91 @@ export interface Env {
[key: string]: any; [key: string]: any;
} }
export interface AppConfig { export interface AppConfig<P, E> extends TemplateSetConfig {
dev?: boolean; props?: P;
env?: Env; env?: E;
translatableAttributes?: string[]; test?: boolean;
translateFn?: (s: string) => string;
templates?: string | Document;
} }
export const DEV_MSG = `Owl is running in 'dev' mode. let hasBeenLogged = false;
export const DEV_MSG = () => {
const hash = (window as any).owl ? (window as any).owl.__info__.hash : "master";
return `Owl is running in 'dev' mode.
This is not suitable for production use. This is not suitable for production use.
See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`; See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
};
export class App<T extends typeof Component = any> extends TemplateSet { export class App<
Root: T; T extends abstract new (...args: any) => any = any,
props: any; P extends object = any,
env: Env = Object.freeze({}); E = any
scheduler = new Scheduler(window.requestAnimationFrame.bind(window)); > extends TemplateSet {
root: ComponentNode | null = null; static validateTarget = validateTarget;
constructor(Root: T, props?: any) { Root: ComponentConstructor<P, E>;
super(); props: P;
env: E;
scheduler = new Scheduler();
root: ComponentNode<P, E> | null = null;
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
super(config);
this.Root = Root; this.Root = Root;
this.props = props; if (config.test) {
this.dev = true;
}
if (this.dev && !config.test && !hasBeenLogged) {
console.info(DEV_MSG());
hasBeenLogged = true;
}
const env = config.env || {};
const descrs = Object.getOwnPropertyDescriptors(env);
this.env = Object.freeze(Object.create(Object.getPrototypeOf(env), descrs));
this.props = config.props || ({} as P);
} }
configure(config: AppConfig): App<T> { mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
if (config.dev) { App.validateTarget(target);
this.dev = config.dev; const node = this.makeNode(this.Root, this.props);
console.info(DEV_MSG); const prom = this.mountNode(node, target, options);
}
if (config.env) {
this.env = Object.freeze(Object.assign({}, config.env));
}
if (config.translateFn) {
this.translateFn = config.translateFn;
}
if (config.translatableAttributes) {
this.translatableAttributes = config.translatableAttributes;
}
if (config.templates) {
this.addTemplates(config.templates);
}
return this;
}
mount(target: HTMLElement, options?: MountOptions): Promise<InstanceType<T>> {
if (!(target instanceof HTMLElement)) {
throw new Error("Cannot mount component: the target is not a valid DOM element");
}
if (!document.body.contains(target)) {
throw new Error("Cannot mount a component on a detached dom node");
}
const node = new ComponentNode(this.Root, this.props, this);
this.root = node; this.root = node;
return node.mountComponent(target, options); return prom;
}
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
return new ComponentNode(Component, props, this);
}
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
const promise: any = new Promise((resolve, reject) => {
let isResolved = false;
// manually set a onMounted callback.
// that way, we are independant from the current node.
node.mounted.push(() => {
resolve(node.component);
isResolved = true;
});
// Manually add the last resort error handler on the node
let handlers = nodeErrorHandlers.get(node);
if (!handlers) {
handlers = [];
nodeErrorHandlers.set(node, handlers);
}
handlers.unshift((e) => {
if (isResolved) {
console.error(e);
} else {
reject(e);
}
throw e;
});
});
node.mountComponent(target, options);
return promise;
} }
destroy() { destroy() {
@@ -74,3 +105,15 @@ export class App<T extends typeof Component = any> extends TemplateSet {
} }
} }
} }
export async function mount<
T extends abstract new (...args: any) => any = any,
P extends object = any,
E = any
>(
C: T & ComponentConstructor<P, E>,
target: HTMLElement,
config: AppConfig<P, E> & MountOptions = {}
): Promise<Component<P, E> & InstanceType<T>> {
return new App(C, config).mount(target, config);
}
+65 -3
View File
@@ -2,6 +2,7 @@ import { BDom, multi, text, toggler } from "../blockdom";
import { validateProps } from "../component/props_validation"; import { validateProps } from "../component/props_validation";
import { Markup } from "../utils"; import { Markup } from "../utils";
import { html } from "../blockdom/index"; import { html } from "../blockdom/index";
import { TARGET } from "../reactivity";
/** /**
* This file contains utility functions that will be injected in each template, * This file contains utility functions that will be injected in each template,
@@ -21,7 +22,8 @@ function callSlot(
extra: any, extra: any,
defaultContent?: (ctx: any, node: any, key: string) => BDom defaultContent?: (ctx: any, node: any, key: string) => BDom
): BDom { ): BDom {
const slots = (ctx.props && ctx.props.slots) || {}; key = key + "__slot_" + name;
const slots = ctx.props[TARGET].slots || {};
const { __render, __ctx, __scope } = slots[name] || {}; const { __render, __ctx, __scope } = slots[name] || {};
const slotScope = Object.create(__ctx || {}); const slotScope = Object.create(__ctx || {});
if (__scope) { if (__scope) {
@@ -101,6 +103,25 @@ function shallowEqual(l1: any[], l2: any[]): boolean {
return true; return true;
} }
class LazyValue {
fn: any;
ctx: any;
node: any;
constructor(fn: any, ctx: any, node: any) {
this.fn = fn;
this.ctx = capture(ctx);
this.node = node;
}
evaluate(): any {
return this.fn(this.ctx, this.node);
}
toString() {
return this.evaluate().toString();
}
}
/* /*
* Safely outputs `value` as a block depending on the nature of `value` * Safely outputs `value` as a block depending on the nature of `value`
*/ */
@@ -113,7 +134,10 @@ export function safeOutput(value: any): ReturnType<typeof toggler> {
if (value instanceof Markup) { if (value instanceof Markup) {
safeKey = `string_safe`; safeKey = `string_safe`;
block = html(value as string); block = html(value as string);
} else if (typeof value === "string") { } else if (value instanceof LazyValue) {
safeKey = `lazy_value`;
block = value.evaluate();
} else if (value instanceof String || typeof value === "string") {
safeKey = "string_unsafe"; safeKey = "string_unsafe";
block = text(value); block = text(value);
} else { } else {
@@ -124,7 +148,42 @@ export function safeOutput(value: any): ReturnType<typeof toggler> {
return toggler(safeKey, block); return toggler(safeKey, block);
} }
export const UTILS = { let boundFunctions = new WeakMap();
function bind(ctx: any, fn: Function): Function {
let component = ctx.__owl__.component;
let boundFnMap = boundFunctions.get(component);
if (!boundFnMap) {
boundFnMap = new WeakMap();
boundFunctions.set(component, boundFnMap);
}
let boundFn = boundFnMap.get(fn);
if (!boundFn) {
boundFn = fn.bind(component);
boundFnMap.set(fn, boundFn);
}
return boundFn;
}
type RefMap = { [key: string]: HTMLElement | null };
type RefSetter = (el: HTMLElement | null) => void;
function multiRefSetter(refs: RefMap, name: string): RefSetter {
let count = 0;
return (el) => {
if (el) {
count++;
if (count > 1) {
throw new Error("Cannot have 2 elements with same ref name at the same time");
}
}
if (count === 0 || el) {
refs[name] = el;
}
};
}
export const helpers = {
withDefault, withDefault,
zero: Symbol("zero"), zero: Symbol("zero"),
isBoundary, isBoundary,
@@ -133,8 +192,11 @@ export const UTILS = {
withKey, withKey,
prepareList, prepareList,
setContextValue, setContextValue,
multiRefSetter,
shallowEqual, shallowEqual,
toNumber, toNumber,
validateProps, validateProps,
LazyValue,
safeOutput, safeOutput,
bind,
}; };
+66 -28
View File
@@ -1,11 +1,12 @@
import { createBlock, html, list, multi, text, toggler } from "../blockdom"; import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
import { compile, Template } from "../compiler"; import { compile, Template } from "../compiler";
import { component } from "../component/component_node"; import { markRaw } from "../reactivity";
import { UTILS } from "./template_helpers"; import { Portal } from "../portal";
import { component, getCurrent } from "../component/component_node";
import { helpers } from "./template_helpers";
import { globalTemplates } from "../utils";
const bdom = { text, createBlock, list, multi, html, toggler, component }; const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
export const globalTemplates: { [key: string]: string | Node } = {};
function parseXML(xml: string): Document { function parseXML(xml: string): Document {
const parser = new DOMParser(); const parser = new DOMParser();
@@ -37,25 +38,52 @@ function parseXML(xml: string): Document {
return doc; return doc;
} }
/**
* Returns the helpers object that will be injected in each template closure
* function
*/
function makeHelpers(getTemplate: (name: string) => Template): any {
return Object.assign({}, helpers, {
Portal,
markRaw,
getTemplate,
call: (owner: any, subTemplate: string, ctx: any, parent: any, key: any) => {
const template = getTemplate(subTemplate);
return toggler(subTemplate, template.call(owner, ctx, parent, key));
},
});
}
export interface TemplateSetConfig {
dev?: boolean;
translatableAttributes?: string[];
translateFn?: (s: string) => string;
templates?: string | Document;
}
export class TemplateSet { export class TemplateSet {
dev: boolean;
rawTemplates: typeof globalTemplates = Object.create(globalTemplates); rawTemplates: typeof globalTemplates = Object.create(globalTemplates);
templates: { [name: string]: Template } = {}; templates: { [name: string]: Template } = {};
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
translatableAttributes?: string[]; translatableAttributes?: string[];
utils: typeof UTILS; helpers: any;
dev?: boolean;
constructor() { constructor(config: TemplateSetConfig = {}) {
const call = (subTemplate: string, ctx: any, parent: any) => { this.dev = config.dev || false;
const template = this.getTemplate(subTemplate); this.translateFn = config.translateFn;
return toggler(subTemplate, template(ctx, parent)); this.translatableAttributes = config.translatableAttributes;
}; if (config.templates) {
this.addTemplates(config.templates);
const getTemplate = (name: string) => this.getTemplate(name); }
this.utils = Object.assign({}, UTILS, { getTemplate, call }); this.helpers = makeHelpers(this.getTemplate.bind(this));
} }
addTemplate(name: string, template: string | Node, options: { allowDuplicate?: boolean } = {}) { addTemplate(
name: string,
template: string | Element,
options: { allowDuplicate?: boolean } = {}
) {
if (name in this.rawTemplates && !options.allowDuplicate) { if (name in this.rawTemplates && !options.allowDuplicate) {
throw new Error(`Template ${name} already defined`); throw new Error(`Template ${name} already defined`);
} }
@@ -70,7 +98,6 @@ export class TemplateSet {
xml = xml instanceof Document ? xml : parseXML(xml); xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) { for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name")!; const name = template.getAttribute("t-name")!;
template.removeAttribute("t-name");
this.addTemplate(name, template, options); this.addTemplate(name, template, options);
} }
} }
@@ -79,21 +106,32 @@ export class TemplateSet {
if (!(name in this.templates)) { if (!(name in this.templates)) {
const rawTemplate = this.rawTemplates[name]; const rawTemplate = this.rawTemplates[name];
if (rawTemplate === undefined) { if (rawTemplate === undefined) {
throw new Error(`Missing template: "${name}"`); let extraInfo = "";
try {
const componentName = getCurrent().component.constructor.name;
extraInfo = ` (for component "${componentName}")`;
} catch {}
throw new Error(`Missing template: "${name}"${extraInfo}`);
} }
const templateFn = compile(rawTemplate, { const templateFn = this._compileTemplate(name, rawTemplate);
name,
dev: this.dev,
translateFn: this.translateFn,
translatableAttributes: this.translatableAttributes,
});
// first add a function to lazily get the template, in case there is a // first add a function to lazily get the template, in case there is a
// recursive call to the template name // recursive call to the template name
this.templates[name] = (context, parent) => this.templates[name](context, parent); const templates = this.templates;
const template = templateFn(bdom, this.utils); this.templates[name] = function (context, parent) {
return templates[name].call(this, context, parent);
};
const template = templateFn(bdom, this.helpers);
this.templates[name] = template; this.templates[name] = template;
} }
return this.templates[name]; return this.templates[name];
} }
_compileTemplate(name: string, template: string | Element) {
return compile(template, {
name,
dev: this.dev,
translateFn: this.translateFn,
translatableAttributes: this.translatableAttributes,
});
}
} }
+17 -5
View File
@@ -1,6 +1,6 @@
import type { Setter } from "./block_compiler"; import type { Setter } from "./block_compiler";
const { setAttribute, removeAttribute } = Element.prototype; const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype;
const tokenList = DOMTokenList.prototype; const tokenList = DOMTokenList.prototype;
const tokenListAdd = tokenList.add; const tokenListAdd = tokenList.add;
const tokenListRemove = tokenList.remove; const tokenListRemove = tokenList.remove;
@@ -14,11 +14,23 @@ const wordRegexp = /\s+/;
* file. * file.
*/ */
function setAttribute(this: HTMLElement, key: string, value: any) {
switch (value) {
case false:
case undefined:
removeAttribute.call(this, key);
break;
case true:
elemSetAttribute.call(this, key, "");
break;
default:
elemSetAttribute.call(this, key, value);
}
}
export function createAttrUpdater(attr: string): Setter<HTMLElement> { export function createAttrUpdater(attr: string): Setter<HTMLElement> {
return function (this: HTMLElement, value: any) { return function (this: HTMLElement, value: any) {
if (value !== false) { setAttribute.call(this, attr, value);
setAttribute.call(this, attr, value === true ? "" : value);
}
}; };
} }
@@ -127,7 +139,7 @@ export function updateClass(this: HTMLElement, val: any, oldVal: any) {
export function makePropSetter(name: string): Setter<HTMLElement> { export function makePropSetter(name: string): Setter<HTMLElement> {
return function setProp(this: HTMLElement, value: any) { return function setProp(this: HTMLElement, value: any) {
(this as any)[name] = value; (this as any)[name] = value || "";
}; };
} }
+92 -77
View File
@@ -113,7 +113,7 @@ interface IntermediateTree {
nextSibling: IntermediateTree | null; nextSibling: IntermediateTree | null;
el: Node; el: Node;
info: DynamicInfo[]; info: DynamicInfo[];
forceRef?: boolean; isRef?: boolean;
refIdx?: number; refIdx?: number;
refN: number; refN: number;
currentNS: string | null; currentNS: string | null;
@@ -127,8 +127,7 @@ function buildTree(
switch (node.nodeType) { switch (node.nodeType) {
case Node.ELEMENT_NODE: { case Node.ELEMENT_NODE: {
// HTMLElement // HTMLElement
let isActive = false; let currentNS = domParentTree && domParentTree.currentNS;
let currentNS = parent && parent.currentNS;
const tagName = (node as Element).tagName; const tagName = (node as Element).tagName;
let el: Node | undefined = undefined; let el: Node | undefined = undefined;
const info: DynamicInfo[] = []; const info: DynamicInfo[] = [];
@@ -136,14 +135,14 @@ function buildTree(
const index = parseInt(tagName.slice(11), 10); const index = parseInt(tagName.slice(11), 10);
info.push({ type: "text", idx: index }); info.push({ type: "text", idx: index });
el = document.createTextNode(""); el = document.createTextNode("");
isActive = true;
} }
if (tagName.startsWith("block-child-")) { if (tagName.startsWith("block-child-")) {
domParentTree!.forceRef = true; if (!domParentTree!.isRef) {
addRef(domParentTree!);
}
const index = parseInt(tagName.slice(12), 10); const index = parseInt(tagName.slice(12), 10);
info.push({ type: "child", idx: index }); info.push({ type: "child", idx: index });
el = document.createTextNode(""); el = document.createTextNode("");
isActive = true;
} }
const attrs = (node as Element).attributes; const attrs = (node as Element).attributes;
const ns = attrs.getNamedItem("block-ns"); const ns = attrs.getNamedItem("block-ns");
@@ -161,7 +160,6 @@ function buildTree(
const attrName = attrs[i].name; const attrName = attrs[i].name;
const attrValue = attrs[i].value; const attrValue = attrs[i].value;
if (attrName.startsWith("block-handler-")) { if (attrName.startsWith("block-handler-")) {
isActive = true;
const idx = parseInt(attrName.slice(14), 10); const idx = parseInt(attrName.slice(14), 10);
info.push({ info.push({
type: "handler", type: "handler",
@@ -169,7 +167,6 @@ function buildTree(
event: attrValue, event: attrValue,
}); });
} else if (attrName.startsWith("block-attribute-")) { } else if (attrName.startsWith("block-attribute-")) {
isActive = true;
const idx = parseInt(attrName.slice(16), 10); const idx = parseInt(attrName.slice(16), 10);
info.push({ info.push({
type: "attribute", type: "attribute",
@@ -178,13 +175,11 @@ function buildTree(
tag: tagName, tag: tagName,
}); });
} else if (attrName === "block-attributes") { } else if (attrName === "block-attributes") {
isActive = true;
info.push({ info.push({
type: "attributes", type: "attributes",
idx: parseInt(attrValue, 10), idx: parseInt(attrValue, 10),
}); });
} else if (attrName === "block-ref") { } else if (attrName === "block-ref") {
isActive = true;
info.push({ info.push({
type: "ref", type: "ref",
idx: parseInt(attrValue, 10), idx: parseInt(attrValue, 10),
@@ -201,7 +196,7 @@ function buildTree(
nextSibling: null, nextSibling: null,
el, el,
info, info,
refN: isActive ? 1 : 0, refN: 0,
currentNS, currentNS,
}; };
@@ -215,8 +210,6 @@ function buildTree(
const tagName = (childNode as Element).tagName; const tagName = (childNode as Element).tagName;
const index = parseInt(tagName.slice(12), 10); const index = parseInt(tagName.slice(12), 10);
info.push({ idx: index, type: "child", isOnlyChild: true }); info.push({ idx: index, type: "child", isOnlyChild: true });
isActive = true;
tree.refN = 1;
} else { } else {
tree.firstChild = buildTree(node.firstChild, tree, tree); tree.firstChild = buildTree(node.firstChild, tree, tree);
el.appendChild(tree.firstChild.el); el.appendChild(tree.firstChild.el);
@@ -229,11 +222,8 @@ function buildTree(
} }
} }
} }
if (isActive) { if (tree.info.length) {
let cur: IntermediateTree | null = tree; addRef(tree);
while ((cur = cur.parent)) {
cur.refN++;
}
} }
return tree; return tree;
} }
@@ -258,6 +248,13 @@ function buildTree(
throw new Error("boom"); throw new Error("boom");
} }
function addRef(tree: IntermediateTree) {
tree.isRef = true;
do {
tree.refN++;
} while ((tree = tree.parent as any));
}
function parentTree(tree: IntermediateTree): IntermediateTree | null { function parentTree(tree: IntermediateTree): IntermediateTree | null {
let parent = tree.parent; let parent = tree.parent;
while (parent && parent.nextSibling === tree) { while (parent && parent.nextSibling === tree) {
@@ -302,23 +299,18 @@ interface BlockCtx {
locations: IndexedLocation[]; locations: IndexedLocation[];
children: Child[]; children: Child[];
cbRefs: number[]; cbRefs: number[];
refList: (() => void)[][];
} }
function buildContext( function buildContext(tree: IntermediateTree, ctx?: BlockCtx, fromIdx?: number): BlockCtx {
tree: IntermediateTree,
ctx?: BlockCtx,
fromIdx?: number,
toIdx?: number
): BlockCtx {
if (!ctx) { if (!ctx) {
const children = new Array(tree.info.filter((v) => v.type === "child").length); const children = new Array(tree.info.filter((v) => v.type === "child").length);
ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN }; ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };
fromIdx = 0; fromIdx = 0;
toIdx = tree.refN - 1;
} }
if (tree.refN) { if (tree.refN) {
const initialIdx = fromIdx!; const initialIdx = fromIdx!;
const isRef = tree.forceRef || tree.info.length > 0; const isRef = tree.isRef;
const firstChild = tree.firstChild ? tree.firstChild.refN : 0; const firstChild = tree.firstChild ? tree.firstChild.refN : 0;
const nextSibling = tree.nextSibling ? tree.nextSibling.refN : 0; const nextSibling = tree.nextSibling ? tree.nextSibling.refN : 0;
@@ -336,13 +328,13 @@ function buildContext(
if (nextSibling) { if (nextSibling) {
const idx = fromIdx! + firstChild; const idx = fromIdx! + firstChild;
ctx.collectors.push({ idx, prevIdx: initialIdx, getVal: nodeGetNextSibling }); ctx.collectors.push({ idx, prevIdx: initialIdx, getVal: nodeGetNextSibling });
buildContext(tree.nextSibling!, ctx, idx, toIdx); buildContext(tree.nextSibling!, ctx, idx);
} }
// left // left
if (firstChild) { if (firstChild) {
ctx.collectors.push({ idx: fromIdx!, prevIdx: initialIdx, getVal: nodeGetFirstChild }); ctx.collectors.push({ idx: fromIdx!, prevIdx: initialIdx, getVal: nodeGetFirstChild });
buildContext(tree.firstChild!, ctx, fromIdx!, toIdx! - nextSibling); buildContext(tree.firstChild!, ctx, fromIdx!);
} }
} }
@@ -417,11 +409,11 @@ function updateCtx(ctx: BlockCtx, tree: IntermediateTree) {
break; break;
} }
case "ref": case "ref":
ctx.cbRefs.push(info.idx); const index = ctx.cbRefs.push(info.idx) - 1;
ctx.locations.push({ ctx.locations.push({
idx: info.idx, idx: info.idx,
refIdx: info.refIdx!, refIdx: info.refIdx!,
setData: setRef, setData: makeRefSetter(index, ctx.refList),
updateData: NO_OP, updateData: NO_OP,
}); });
} }
@@ -435,12 +427,21 @@ function buildBlock(template: HTMLElement, ctx: BlockCtx): BlockType {
let B = createBlockClass(template, ctx); let B = createBlockClass(template, ctx);
if (ctx.cbRefs.length) { if (ctx.cbRefs.length) {
const refs = ctx.cbRefs; const cbRefs = ctx.cbRefs;
const refList = ctx.refList;
let cbRefsNumber = cbRefs.length;
B = class extends B { B = class extends B {
mount(parent: HTMLElement, afterNode: Node | null) {
refList.push(new Array(cbRefsNumber));
super.mount(parent, afterNode);
for (let cbRef of refList.pop()!) {
cbRef();
}
}
remove() { remove() {
super.remove(); super.remove();
for (let ref of refs) { for (let cbRef of cbRefs) {
let fn = (this as any).data[ref]; let fn = (this as any).data[cbRef];
fn(null); fn(null);
} }
} }
@@ -485,12 +486,12 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
const nodeInsertBefore = nodeProto.insertBefore; const nodeInsertBefore = nodeProto.insertBefore;
const elementRemove = elementProto.remove; const elementRemove = elementProto.remove;
return class Block { class Block {
el: HTMLElement | undefined; el: HTMLElement | undefined;
refs: Node[] | undefined;
data: any[] | undefined;
parentEl?: HTMLElement | undefined; parentEl?: HTMLElement | undefined;
data: any[] | undefined;
children?: (VNode | undefined)[]; children?: (VNode | undefined)[];
refs: Node[] | undefined;
constructor(data?: any[]) { constructor(data?: any[]) {
this.data = data; this.data = data;
@@ -511,46 +512,62 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
nodeInsertBefore.call(this.parentEl, this.el!, target); nodeInsertBefore.call(this.parentEl, this.el!, target);
} }
toString() {
const div = document.createElement("div");
this.mount(div, null);
return div.innerHTML;
}
mount(parent: HTMLElement, afterNode: Node | null) { mount(parent: HTMLElement, afterNode: Node | null) {
const el = nodeCloneNode.call(template, true); const el = nodeCloneNode.call(template, true) as HTMLElement;
nodeInsertBefore.call(parent, el, afterNode); nodeInsertBefore.call(parent, el, afterNode);
if (isDynamic) { this.el = el;
// collecting references this.parentEl = parent;
const refs: Node[] = new Array(refN); }
this.refs = refs; patch(other: Block, withBeforeRemove: boolean) {}
refs[0] = el; }
for (let i = 0; i < colN; i++) {
const w = collectors[i];
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
}
// applying data to all update points if (isDynamic) {
if (locN) { Block.prototype.mount = function mount(parent: HTMLElement, afterNode: Node | null) {
const data = this.data!; const el = nodeCloneNode.call(template, true);
for (let i = 0; i < locN; i++) { // collecting references
const loc = locations[i]; const refs: Node[] = new Array(refN);
loc.setData.call(refs[loc.refIdx], data[i]); this.refs = refs;
} refs[0] = el;
} for (let i = 0; i < colN; i++) {
const w = collectors[i];
refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
}
// preparing all children // applying data to all update points
if (childN) { if (locN) {
const children = this.children; const data = this.data!;
for (let i = 0; i < childN; i++) { for (let i = 0; i < locN; i++) {
const child = children![i]; const loc = locations[i];
if (child) { loc.setData.call(refs[loc.refIdx], data[i]);
const loc = childrenLocs[i]; }
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null; }
child.isOnlyChild = loc.isOnlyChild;
child.mount(refs[loc.parentRefIdx] as any, afterNode); nodeInsertBefore.call(parent, el, afterNode);
}
// preparing all children
if (childN) {
const children = this.children;
for (let i = 0; i < childN; i++) {
const child = children![i];
if (child) {
const loc = childrenLocs[i];
const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
child.isOnlyChild = loc.isOnlyChild;
child.mount(refs[loc.parentRefIdx] as any, afterNode);
} }
} }
} }
this.el = el as HTMLElement; this.el = el as HTMLElement;
this.parentEl = parent; this.parentEl = parent;
} };
patch(other: Block, withBeforeRemove: boolean) {
Block.prototype.patch = function patch(other: Block, withBeforeRemove: boolean) {
if (this === other) { if (this === other) {
return; return;
} }
@@ -595,19 +612,17 @@ function createBlockClass(template: HTMLElement, ctx: BlockCtx): BlockClass {
} }
} }
} }
} };
toString() { }
const div = document.createElement("div"); return Block;
this.mount(div, null);
return div.innerHTML;
}
};
} }
function setText(this: Text, value: any) { function setText(this: Text, value: any) {
characterDataSetData.call(this, toText(value)); characterDataSetData.call(this, toText(value));
} }
function setRef(this: HTMLElement, fn: any) { function makeRefSetter(index: number, refs: (() => void)[][]): Setter<HTMLElement> {
fn(this); return function setRef(this: HTMLElement, fn: any) {
refs[refs.length - 1][index] = () => fn(this);
};
} }
+1
View File
@@ -61,6 +61,7 @@ class VHtml {
// remove current content // remove current content
this.remove(); this.remove();
this.content = content; this.content = content;
this.html = other.html;
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ export { toggler } from "./toggler";
export { createBlock } from "./block_compiler"; export { createBlock } from "./block_compiler";
export { list } from "./list"; export { list } from "./list";
export { multi } from "./multi"; export { multi } from "./multi";
export { text } from "./text"; export { text, comment } from "./text";
export { html } from "./html"; export { html } from "./html";
export interface VNode<T = any> { export interface VNode<T = any> {
-1
View File
@@ -98,7 +98,6 @@ class VList {
let endVn2 = ch2[endIdx2]; let endVn2 = ch2[endIdx2];
let mapping: any = undefined; let mapping: any = undefined;
// let noFullRemove = this.hasNoComponent;
while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) { while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
+1 -1
View File
@@ -125,7 +125,7 @@ export class VMulti {
} }
toString(): string { toString(): string {
return this.children.map((c) => c!.toString()).join(""); return this.children.map((c) => (c ? c!.toString() : "")).join("");
} }
} }
+32 -15
View File
@@ -8,18 +8,17 @@ const nodeInsertBefore = nodeProto.insertBefore;
const characterDataSetData = getDescriptor(characterDataProto, "data").set!; const characterDataSetData = getDescriptor(characterDataProto, "data").set!;
const nodeRemoveChild = nodeProto.removeChild; const nodeRemoveChild = nodeProto.removeChild;
class VText { abstract class VSimpleNode {
text: string; text: string | String;
parentEl?: HTMLElement | undefined; parentEl?: HTMLElement | undefined;
el?: Text; el?: any;
constructor(text: string) { constructor(text: string | String) {
this.text = text; this.text = text;
} }
mount(parent: HTMLElement, afterNode: Node | null) { mountNode(node: Node, parent: HTMLElement, afterNode: Node | null) {
this.parentEl = parent; this.parentEl = parent;
const node = document.createTextNode(toText(this.text));
nodeInsertBefore.call(parent, node, afterNode); nodeInsertBefore.call(parent, node, afterNode);
this.el = node; this.el = node;
} }
@@ -29,14 +28,6 @@ class VText {
nodeInsertBefore.call(this.parentEl, this.el!, target); nodeInsertBefore.call(this.parentEl, this.el!, target);
} }
patch(other: VText) {
const text2 = other.text;
if (this.text !== text2) {
characterDataSetData.call(this.el!, toText(text2));
this.text = text2;
}
}
beforeRemove() {} beforeRemove() {}
remove() { remove() {
@@ -52,10 +43,36 @@ class VText {
} }
} }
export function text(str: string): VNode<VText> { class VText extends VSimpleNode {
mount(parent: HTMLElement, afterNode: Node | null) {
this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode);
}
patch(other: VText) {
const text2 = other.text;
if (this.text !== text2) {
characterDataSetData.call(this.el!, toText(text2));
this.text = text2;
}
}
}
class VComment extends VSimpleNode {
mount(parent: HTMLElement, afterNode: Node | null) {
this.mountNode(document.createComment(toText(this.text)), parent, afterNode);
}
patch() {}
}
export function text(str: string | String): VNode<VText> {
return new VText(str); return new VText(str);
} }
export function comment(str: string): VNode<VComment> {
return new VComment(str);
}
export function toText(value: any): string { export function toText(value: any): string {
switch (typeof value) { switch (typeof value) {
case "string": case "string":
+280 -164
View File
@@ -19,9 +19,10 @@ import {
ASTTSet, ASTTSet,
ASTTranslation, ASTTranslation,
ASTType, ASTType,
ASTTPortal,
} from "./parser"; } from "./parser";
type BlockType = "block" | "text" | "multi" | "list" | "html"; type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
export interface Config { export interface Config {
translateFn?: (s: string) => string; translateFn?: (s: string) => string;
@@ -31,19 +32,25 @@ export interface Config {
export interface CodeGenOptions extends Config { export interface CodeGenOptions extends Config {
hasSafeContext?: boolean; hasSafeContext?: boolean;
name?: string;
} }
// using a non-html document so that <inner/outer>HTML serializes as XML instead // using a non-html document so that <inner/outer>HTML serializes as XML instead
// of HTML (as we will parse it as xml later) // of HTML (as we will parse it as xml later)
const xmlDoc = document.implementation.createDocument(null, null, null); const xmlDoc = document.implementation.createDocument(null, null, null);
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// BlockDescription // BlockDescription
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
class BlockDescription { class BlockDescription {
static nextBlockId = 1; static nextBlockId = 1;
static nextDataId = 1; static nextDataIds: { [key: string]: number } = {};
static generateId(prefix: string) {
this.nextDataIds[prefix] = (this.nextDataIds[prefix] || 0) + 1;
return prefix + this.nextDataIds[prefix];
}
varName: string; varName: string;
blockName: string; blockName: string;
@@ -68,8 +75,8 @@ class BlockDescription {
this.type = type; this.type = type;
} }
insertData(str: string): number { insertData(str: string, prefix: string = "d"): number {
const id = "d" + BlockDescription.nextDataId++; const id = BlockDescription.generateId(prefix);
this.target.addLine(`let ${id} = ${str};`); this.target.addLine(`let ${id} = ${str};`);
return this.data.push(id) - 1; return this.data.push(id) - 1;
} }
@@ -120,6 +127,8 @@ interface Context {
isLast?: boolean; isLast?: boolean;
translate: boolean; translate: boolean;
tKeyExpr: string | null; tKeyExpr: string | null;
nameSpace?: string;
tModelSelectedExpr?: string;
} }
function createContext(parentCtx: Context, params?: Partial<Context>) { function createContext(parentCtx: Context, params?: Partial<Context>) {
@@ -130,6 +139,8 @@ function createContext(parentCtx: Context, params?: Partial<Context>) {
forceNewBlock: true, forceNewBlock: true,
translate: parentCtx.translate, translate: parentCtx.translate,
tKeyExpr: null, tKeyExpr: null,
nameSpace: parentCtx.nameSpace,
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
}, },
params params
); );
@@ -142,6 +153,10 @@ class CodeTarget {
code: string[] = []; code: string[] = [];
hasRoot = false; hasRoot = false;
hasCache = false; hasCache = false;
hasRef: boolean = false;
// maps ref name to [id, expr]
refInfo: { [name: string]: [string, string] } = {};
shouldProtectScope: boolean = false;
constructor(name: string) { constructor(name: string) {
this.name = name; this.name = name;
@@ -155,6 +170,34 @@ class CodeTarget {
this.code.splice(idx, 0, prefix + line); this.code.splice(idx, 0, prefix + line);
} }
} }
generateCode(): string {
let result: string[] = [];
result.push(`function ${this.name}(ctx, node, key = "") {`);
if (this.hasRef) {
result.push(` const refs = ctx.__owl__.refs;`);
for (let name in this.refInfo) {
const [id, expr] = this.refInfo[name];
result.push(` const ${id} = ${expr};`);
}
}
if (this.shouldProtectScope) {
result.push(` ctx = Object.create(ctx);`);
result.push(` ctx[isBoundary] = 1`);
}
if (this.hasCache) {
result.push(` let cache = ctx.cache || {};`);
result.push(` let nextCache = ctx.cache = {};`);
}
for (let line of this.code) {
result.push(line);
}
if (!this.hasRoot) {
result.push(`return text('');`);
}
result.push(`}`);
return result.join("\n ");
}
} }
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"]; const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
@@ -162,36 +205,44 @@ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
export class CodeGenerator { export class CodeGenerator {
blocks: BlockDescription[] = []; blocks: BlockDescription[] = [];
nextId = 1; ids: { [key: string]: number } = {};
nextBlockId = 1; nextBlockId = 1;
shouldProtectScope: boolean = false;
shouldDefineAssign: boolean = false;
hasSafeContext: boolean; hasSafeContext: boolean;
hasRef: boolean = false;
isDebug: boolean = false; isDebug: boolean = false;
functions: CodeTarget[] = []; targets: CodeTarget[] = [];
target = new CodeTarget("main"); target = new CodeTarget("template");
templateName: string; templateName?: string;
dev: boolean; dev: boolean;
translateFn: (s: string) => string; translateFn: (s: string) => string;
translatableAttributes: string[]; translatableAttributes: string[] = TRANSLATABLE_ATTRS;
ast: AST; ast: AST;
staticCalls: { id: string; template: string }[] = []; staticCalls: { id: string; template: string }[] = [];
helpers: Set<string> = new Set();
constructor(name: string, ast: AST, options: CodeGenOptions) { constructor(ast: AST, options: CodeGenOptions) {
this.translateFn = options.translateFn || ((s: string) => s); this.translateFn = options.translateFn || ((s: string) => s);
this.translatableAttributes = options.translatableAttributes || TRANSLATABLE_ATTRS; if (options.translatableAttributes) {
const attrs = new Set(TRANSLATABLE_ATTRS);
for (let attr of options.translatableAttributes) {
if (attr.startsWith("-")) {
attrs.delete(attr.slice(1));
} else {
attrs.add(attr);
}
}
this.translatableAttributes = [...attrs];
}
this.hasSafeContext = options.hasSafeContext || false; this.hasSafeContext = options.hasSafeContext || false;
this.dev = options.dev || false; this.dev = options.dev || false;
this.ast = ast; this.ast = ast;
this.templateName = name; this.templateName = options.name;
} }
generateCode(): string { generateCode(): string {
const ast = this.ast; const ast = this.ast;
this.isDebug = ast.type === ASTType.TDebug; this.isDebug = ast.type === ASTType.TDebug;
BlockDescription.nextBlockId = 1; BlockDescription.nextBlockId = 1;
BlockDescription.nextDataId = 1; BlockDescription.nextDataIds = {};
this.compileAST(ast, { this.compileAST(ast, {
block: null, block: null,
index: 0, index: 0,
@@ -200,68 +251,50 @@ export class CodeGenerator {
translate: true, translate: true,
tKeyExpr: null, tKeyExpr: null,
}); });
let mainCode = this.target.code;
this.target.code = [];
this.target.indentLevel = 0;
// define blocks and utility functions // define blocks and utility functions
this.addLine(`let { text, createBlock, list, multi, html, toggler, component } = bdom;`); let mainCode = [
this.addLine( ` let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;`,
`let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;` ];
); if (this.helpers.size) {
if (this.shouldDefineAssign) { mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
this.addLine(`let assign = Object.assign;`); }
if (this.templateName) {
mainCode.push(`// Template name: "${this.templateName}"`);
} }
for (let { id, template } of this.staticCalls) { for (let { id, template } of this.staticCalls) {
this.addLine(`const ${id} = getTemplate(${template});`); mainCode.push(`const ${id} = getTemplate(${template});`);
} }
// define all blocks // define all blocks
if (this.blocks.length) { if (this.blocks.length) {
this.addLine(``); mainCode.push(``);
for (let block of this.blocks) { for (let block of this.blocks) {
if (block.dom) { if (block.dom) {
let xmlString = block.asXmlString(); let xmlString = block.asXmlString();
if (block.dynamicTagName) { if (block.dynamicTagName) {
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`); xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`); xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
this.addLine(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`); mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
} else { } else {
this.addLine(`let ${block.blockName} = createBlock(\`${xmlString}\`);`); mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
} }
} }
} }
} }
// define all slots // define all slots/defaultcontent function
for (let fn of this.functions) { if (this.targets.length) {
this.generateFunctions(fn); for (let fn of this.targets) {
mainCode.push("");
mainCode = mainCode.concat(fn.generateCode());
}
} }
// // generate main code // generate main code
this.target.indentLevel = 0; mainCode.push("");
this.addLine(``); mainCode = mainCode.concat("return " + this.target.generateCode());
this.addLine(`return function template(ctx, node, key = "") {`); const code = mainCode.join("\n ");
if (this.hasRef) {
this.addLine(` const refs = ctx.__owl__.refs;`);
}
if (this.shouldProtectScope) {
this.addLine(` ctx = Object.create(ctx);`);
this.addLine(` ctx[isBoundary] = 1`);
}
if (this.target.hasCache) {
this.addLine(` let cache = ctx.cache || {};`);
this.addLine(` let nextCache = ctx.cache = {};`);
}
for (let line of mainCode) {
this.addLine(line);
}
if (!this.target.hasRoot) {
throw new Error("missing root block");
}
this.addLine("}");
const code = this.target.code.join("\n");
if (this.isDebug) { if (this.isDebug) {
const msg = `[Owl Debug]\n${code}`; const msg = `[Owl Debug]\n${code}`;
@@ -270,16 +303,25 @@ export class CodeGenerator {
return code; return code;
} }
compileInNewTarget(prefix: string, ast: AST, ctx: Context): string {
const name = this.generateId(prefix);
const initialTarget = this.target;
const target = new CodeTarget(name);
this.targets.push(target);
this.target = target;
const subCtx: Context = createContext(ctx);
this.compileAST(ast, subCtx);
this.target = initialTarget;
return name;
}
addLine(line: string) { addLine(line: string) {
this.target.addLine(line); this.target.addLine(line);
} }
generateId(prefix: string = ""): string { generateId(prefix: string = ""): string {
return `${prefix}${this.nextId++}`; this.ids[prefix] = (this.ids[prefix] || 0) + 1;
} return prefix + this.ids[prefix];
generateBlockName(): string {
return `block${this.blocks.length + 1}`;
} }
insertAnchor(block: BlockDescription) { insertAnchor(block: BlockDescription) {
@@ -316,6 +358,7 @@ export class CodeGenerator {
if (tKeyExpr) { if (tKeyExpr) {
keyArg = `${tKeyExpr} + ${keyArg}`; keyArg = `${tKeyExpr} + ${keyArg}`;
} }
this.helpers.add("withKey");
this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`); this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
return; return;
} }
@@ -331,18 +374,6 @@ export class CodeGenerator {
} }
} }
generateFunctions(fn: CodeTarget) {
this.addLine("");
this.addLine(`function ${fn.name}(ctx, node, key) {`);
if (fn.hasCache) {
this.addLine(`let cache = ctx.cache || {};`);
this.addLine(`let nextCache = ctx.cache = {};`);
}
for (let line of fn.code) {
this.addLine(line);
}
this.addLine(`}`);
}
/** /**
* Captures variables that are used inside of an expression. This is useful * Captures variables that are used inside of an expression. This is useful
* because in compiled code, almost all variables are accessed through the ctx * because in compiled code, almost all variables are accessed through the ctx
@@ -430,6 +461,8 @@ export class CodeGenerator {
case ASTType.TTranslation: case ASTType.TTranslation:
this.compileTTranslation(ast, ctx); this.compileTTranslation(ast, ctx);
break; break;
case ASTType.TPortal:
this.compileTPortal(ast, ctx);
} }
} }
@@ -450,13 +483,14 @@ export class CodeGenerator {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock; const isNewBlock = !block || forceNewBlock;
if (isNewBlock) { if (isNewBlock) {
block = this.createBlock(block, "block", ctx); block = this.createBlock(block, "comment", ctx);
this.blocks.push(block); this.insertBlock(`comment(\`${ast.value}\`)`, block, {
} ...ctx,
const text = xmlDoc.createComment(ast.value); forceNewBlock: forceNewBlock && !block,
block!.insert(text); });
if (isNewBlock) { } else {
this.insertBlock("", block!, ctx); const text = xmlDoc.createComment(ast.value);
block!.insert(text);
} }
} }
@@ -485,7 +519,12 @@ export class CodeGenerator {
const modifiers = rawEvent const modifiers = rawEvent
.split(".") .split(".")
.slice(1) .slice(1)
.map((m) => `"${m}"`); .map((m) => {
if (!MODS.has(m)) {
throw new Error(`Unknown event modifier: '${m}'`);
}
return `"${m}"`;
});
let modifiersCode = ""; let modifiersCode = "";
if (modifiers.length) { if (modifiers.length) {
modifiersCode = `${modifiers.join(",")}, `; modifiersCode = `${modifiers.join(",")}, `;
@@ -495,10 +534,10 @@ export class CodeGenerator {
compileTDomNode(ast: ASTDomNode, ctx: Context) { compileTDomNode(ast: ASTDomNode, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null; const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
let codeIdx = this.target.code.length; let codeIdx = this.target.code.length;
if (isNewBlock) { if (isNewBlock) {
if (ast.dynamicTag && ctx.block) { if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
this.insertAnchor(ctx.block!); this.insertAnchor(ctx.block!);
} }
block = this.createBlock(block, "block", ctx); block = this.createBlock(block, "block", ctx);
@@ -511,57 +550,84 @@ export class CodeGenerator {
} }
// attributes // attributes
const attrs: { [key: string]: string } = {}; const attrs: { [key: string]: string } = {};
if (ast.ns) { const nameSpace = ast.ns || ctx.nameSpace;
if (nameSpace && isNewBlock) {
// specific namespace uri // specific namespace uri
attrs["block-ns"] = ast.ns; attrs["block-ns"] = nameSpace;
} }
for (let key in ast.attrs) { for (let key in ast.attrs) {
let expr, attrName;
if (key.startsWith("t-attf")) { if (key.startsWith("t-attf")) {
let expr = interpolate(ast.attrs[key]); expr = interpolate(ast.attrs[key]);
const idx = block!.insertData(expr); const idx = block!.insertData(expr, "attr");
attrs["block-attribute-" + idx] = key.slice(7);
attrName = key.slice(7);
attrs["block-attribute-" + idx] = attrName;
} else if (key.startsWith("t-att")) { } else if (key.startsWith("t-att")) {
let expr = compileExpr(ast.attrs[key]); expr = compileExpr(ast.attrs[key]);
const idx = block!.insertData(expr); const idx = block!.insertData(expr, "attr");
if (key === "t-att") { if (key === "t-att") {
attrs[`block-attributes`] = String(idx); attrs[`block-attributes`] = String(idx);
} else { } else {
attrs[`block-attribute-${idx}`] = key.slice(6); attrName = key.slice(6);
attrs[`block-attribute-${idx}`] = attrName;
} }
} else if (this.translatableAttributes.includes(key)) { } else if (this.translatableAttributes.includes(key)) {
attrs[key] = this.translateFn(ast.attrs[key]); attrs[key] = this.translateFn(ast.attrs[key]);
} else { } else {
expr = `"${ast.attrs[key]}"`;
attrName = key;
attrs[key] = ast.attrs[key]; attrs[key] = ast.attrs[key];
} }
if (attrName === "value" && ctx.tModelSelectedExpr) {
let selectedId = block!.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
attrs[`block-attribute-${selectedId}`] = "selected";
}
} }
// event handlers // event handlers
for (let ev in ast.on) { for (let ev in ast.on) {
const name = this.generateHandlerCode(ev, ast.on[ev]); const name = this.generateHandlerCode(ev, ast.on[ev]);
const idx = block!.insertData(name); const idx = block!.insertData(name, "hdlr");
attrs[`block-handler-${idx}`] = ev; attrs[`block-handler-${idx}`] = ev;
} }
// t-ref // t-ref
if (ast.ref) { if (ast.ref) {
this.hasRef = true; this.target.hasRef = true;
const isDynamic = INTERP_REGEXP.test(ast.ref); const isDynamic = INTERP_REGEXP.test(ast.ref);
if (isDynamic) { if (isDynamic) {
const str = ast.ref.replace( const str = ast.ref.replace(
INTERP_REGEXP, INTERP_REGEXP,
(expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}" (expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}"
); );
const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`); const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`, "ref");
attrs["block-ref"] = String(idx); attrs["block-ref"] = String(idx);
} else { } else {
const idx = block!.insertData(`(el) => refs[\`${ast.ref}\`] = el`); let name = ast.ref;
attrs["block-ref"] = String(idx); if (name in this.target.refInfo) {
// ref has already been defined
this.helpers.add("multiRefSetter");
const info = this.target.refInfo[name];
const index = block!.data.push(info[0]) - 1;
attrs["block-ref"] = String(index);
info[1] = `multiRefSetter(refs, \`${name}\`)`;
} else {
let id = this.generateId("ref");
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
const index = block!.data.push(id) - 1;
attrs["block-ref"] = String(index);
}
} }
} }
// t-model // t-model
let tModelSelectedExpr;
if (ast.model) { if (ast.model) {
const { const {
hasDynamicChildren,
baseExpr, baseExpr,
expr, expr,
eventType, eventType,
@@ -572,25 +638,34 @@ export class CodeGenerator {
} = ast.model; } = ast.model;
const baseExpression = compileExpr(baseExpr); const baseExpression = compileExpr(baseExpr);
const id = this.generateId(); const bExprId = this.generateId("bExpr");
this.addLine(`const bExpr${id} = ${baseExpression};`); this.addLine(`const ${bExprId} = ${baseExpression};`);
const expression = compileExpr(expr); const expression = compileExpr(expr);
const exprId = this.generateId("expr");
this.addLine(`const ${exprId} = ${expression};`);
const fullExpression = `${bExprId}[${exprId}]`;
let idx: number; let idx: number;
if (specialInitTargetAttr) { if (specialInitTargetAttr) {
idx = block!.insertData(`${baseExpression}[${expression}] === '${attrs[targetAttr]}'`); idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
attrs[`block-attribute-${idx}`] = specialInitTargetAttr; attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = this.generateId("bValue");
tModelSelectedExpr = `${bValueId}`;
this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
} else { } else {
idx = block!.insertData(`${baseExpression}[${expression}]`); idx = block!.insertData(`${fullExpression}`, "attr");
attrs[`block-attribute-${idx}`] = targetAttr; attrs[`block-attribute-${idx}`] = targetAttr;
} }
this.helpers.add("toNumber");
let valueCode = `ev.target.${targetAttr}`; let valueCode = `ev.target.${targetAttr}`;
valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode; valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode; valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
const handler = `[(ev) => { bExpr${id}[${expression}] = ${valueCode}; }]`; const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
idx = block!.insertData(handler); idx = block!.insertData(handler, "hdlr");
attrs[`block-handler-${idx}`] = eventType; attrs[`block-handler-${idx}`] = eventType;
} }
@@ -612,6 +687,9 @@ export class CodeGenerator {
index: block!.childNumber, index: block!.childNumber,
forceNewBlock: false, forceNewBlock: false,
isLast: ctx.isLast && i === children.length - 1, isLast: ctx.isLast && i === children.length - 1,
tKeyExpr: ctx.tKeyExpr,
nameSpace,
tModelSelectedExpr,
}); });
this.compileAST(child, subCtx); this.compileAST(child, subCtx);
} }
@@ -626,7 +704,7 @@ export class CodeGenerator {
const children = block!.children.slice(); const children = block!.children.slice();
let current = children.shift(); let current = children.shift();
for (let i = codeIdx; i < code.length; i++) { for (let i = codeIdx; i < code.length; i++) {
if (code[i].trimStart().startsWith(`let ${current!.varName}`)) { if (code[i].trimStart().startsWith(`let ${current!.varName} `)) {
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName); code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
current = children.shift(); current = children.shift();
if (!current) break; if (!current) break;
@@ -641,10 +719,12 @@ export class CodeGenerator {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let expr: string; let expr: string;
if (ast.expr === "0") { if (ast.expr === "0") {
this.helpers.add("zero");
expr = `ctx[zero]`; expr = `ctx[zero]`;
} else { } else {
expr = compileExpr(ast.expr); expr = compileExpr(ast.expr);
if (ast.defaultValue) { if (ast.defaultValue) {
this.helpers.add("withDefault");
expr = `withDefault(${expr}, \`${ast.defaultValue}\`)`; expr = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
} }
} }
@@ -652,7 +732,7 @@ export class CodeGenerator {
block = this.createBlock(block, "text", ctx); block = this.createBlock(block, "text", ctx);
this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block }); this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block });
} else { } else {
const idx = block.insertData(expr); const idx = block.insertData(expr, "txt");
const text = xmlDoc.createElement(`block-text-${idx}`); const text = xmlDoc.createElement(`block-text-${idx}`);
block.insert(text); block.insert(text);
} }
@@ -664,11 +744,13 @@ export class CodeGenerator {
this.insertAnchor(block); this.insertAnchor(block);
} }
block = this.createBlock(block, "html", ctx); block = this.createBlock(block, "html", ctx);
this.helpers.add(ast.expr === "0" ? "zero" : "safeOutput");
let expr = ast.expr === "0" ? "ctx[zero]" : `safeOutput(${compileExpr(ast.expr)})`; let expr = ast.expr === "0" ? "ctx[zero]" : `safeOutput(${compileExpr(ast.expr)})`;
if (ast.body) { if (ast.body) {
const nextId = BlockDescription.nextBlockId; const nextId = BlockDescription.nextBlockId;
const subCtx: Context = createContext(ctx); const subCtx: Context = createContext(ctx);
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx); this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
this.helpers.add("withDefault");
expr = `withDefault(${expr}, b${nextId})`; expr = `withDefault(${expr}, b${nextId})`;
} }
this.insertBlock(`${expr}`, block, ctx); this.insertBlock(`${expr}`, block, ctx);
@@ -717,7 +799,7 @@ export class CodeGenerator {
const children = block!.children.slice(); const children = block!.children.slice();
let current = children.shift(); let current = children.shift();
for (let i = codeIdx; i < code.length; i++) { for (let i = codeIdx; i < code.length; i++) {
if (code[i].trimStart().startsWith(`let ${current!.varName}`)) { if (code[i].trimStart().startsWith(`let ${current!.varName} `)) {
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName); code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
current = children.shift(); current = children.shift();
if (!current) break; if (!current) break;
@@ -745,6 +827,7 @@ export class CodeGenerator {
const keys = `k_block${block.id}`; const keys = `k_block${block.id}`;
const l = `l_block${block.id}`; const l = `l_block${block.id}`;
const c = `c_block${block.id}`; const c = `c_block${block.id}`;
this.helpers.add("prepareList");
this.addLine( this.addLine(
`const [${keys}, ${vals}, ${l}, ${c}] = prepareList(${compileExpr(ast.collection)});` `const [${keys}, ${vals}, ${l}, ${c}] = prepareList(${compileExpr(ast.collection)});`
); );
@@ -778,7 +861,6 @@ export class CodeGenerator {
let id: string; let id: string;
if (ast.memo) { if (ast.memo) {
this.target.hasCache = true; this.target.hasCache = true;
this.shouldDefineAssign = true;
id = this.generateId(); id = this.generateId();
this.addLine(`let memo${id} = ${compileExpr(ast.memo)}`); this.addLine(`let memo${id} = ${compileExpr(ast.memo)}`);
this.addLine(`let vnode${id} = cache[key${this.target.loopLevel}];`); this.addLine(`let vnode${id} = cache[key${this.target.loopLevel}];`);
@@ -799,7 +881,9 @@ export class CodeGenerator {
this.compileAST(ast.body, subCtx); this.compileAST(ast.body, subCtx);
if (ast.memo) { if (ast.memo) {
this.addLine( this.addLine(
`nextCache[key${this.target.loopLevel}] = assign(${c}[${loopVar}], {memo: memo${id!}});` `nextCache[key${
this.target.loopLevel
}] = Object.assign(${c}[${loopVar}], {memo: memo${id!}});`
); );
} }
this.target.indentLevel--; this.target.indentLevel--;
@@ -859,7 +943,7 @@ export class CodeGenerator {
const children = block!.children.slice(); const children = block!.children.slice();
let current = children.shift(); let current = children.shift();
for (let i = codeIdx; i < code.length; i++) { for (let i = codeIdx; i < code.length; i++) {
if (code[i].trimStart().startsWith(`let ${current!.varName}`)) { if (code[i].trimStart().startsWith(`let ${current!.varName} `)) {
code[i] = code[i].replace(`let ${current!.varName}`, current!.varName); code[i] = code[i].replace(`let ${current!.varName}`, current!.varName);
current = children.shift(); current = children.shift();
if (!current) break; if (!current) break;
@@ -879,10 +963,12 @@ export class CodeGenerator {
if (ast.body) { if (ast.body) {
this.addLine(`ctx = Object.create(ctx);`); this.addLine(`ctx = Object.create(ctx);`);
this.addLine(`ctx[isBoundary] = 1;`); this.addLine(`ctx[isBoundary] = 1;`);
this.helpers.add("isBoundary");
const nextId = BlockDescription.nextBlockId; const nextId = BlockDescription.nextBlockId;
const subCtx: Context = createContext(ctx, { preventRoot: true }); const subCtx: Context = createContext(ctx, { preventRoot: true });
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx); this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
if (nextId !== BlockDescription.nextBlockId) { if (nextId !== BlockDescription.nextBlockId) {
this.helpers.add("zero");
this.addLine(`ctx[zero] = b${nextId};`); this.addLine(`ctx[zero] = b${nextId};`);
} }
} }
@@ -898,12 +984,14 @@ export class CodeGenerator {
const templateVar = this.generateId("template"); const templateVar = this.generateId("template");
this.addLine(`const ${templateVar} = ${subTemplate};`); this.addLine(`const ${templateVar} = ${subTemplate};`);
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(`call(${templateVar}, ctx, node, ${key})`, block!, { this.helpers.add("call");
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block!, {
...ctx, ...ctx,
forceNewBlock: !block, forceNewBlock: !block,
}); });
} else { } else {
const id = this.generateId(`callTemplate_`); const id = this.generateId(`callTemplate_`);
this.helpers.add("getTemplate");
this.staticCalls.push({ id, template: subTemplate }); this.staticCalls.push({ id, template: subTemplate });
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block!, { this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block!, {
@@ -928,13 +1016,15 @@ export class CodeGenerator {
} }
compileTSet(ast: ASTTSet, ctx: Context) { compileTSet(ast: ASTTSet, ctx: Context) {
this.shouldProtectScope = true; this.target.shouldProtectScope = true;
this.helpers.add("isBoundary").add("withDefault");
const expr = ast.value ? compileExpr(ast.value || "") : "null"; const expr = ast.value ? compileExpr(ast.value || "") : "null";
if (ast.body) { if (ast.body) {
const subCtx: Context = createContext(ctx); this.helpers.add("LazyValue");
const nextId = `b${BlockDescription.nextBlockId}`; const bodyAst: AST = { type: ASTType.Multi, content: ast.body };
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx); const name = this.compileInNewTarget("value", bodyAst, ctx);
const value = ast.value ? (nextId ? `withDefault(${expr}, ${nextId})` : expr) : nextId; let value = `new LazyValue(${name}, ctx, node)`;
value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
this.addLine(`ctx[\`${ast.name}\`] = ${value};`); this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
} else { } else {
let value: string; let value: string;
@@ -947,6 +1037,7 @@ export class CodeGenerator {
} else { } else {
value = expr; value = expr;
} }
this.helpers.add("setContextValue");
this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`); this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
} }
} }
@@ -959,18 +1050,50 @@ export class CodeGenerator {
return parts.join("__"); return parts.join("__");
} }
/**
* Formats a prop name and value into a string suitable to be inserted in the
* generated code. For example:
*
* Name Value Result
* ---------------------------------------------------------
* "number" "state" "number: ctx['state']"
* "something" "" "something: undefined"
* "some-prop" "state" "'some-prop': ctx['state']"
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
*/
formatProp(name: string, value: string): string {
value = this.captureExpression(value);
if (name.includes(".")) {
let [_name, suffix] = name.split(".");
if (suffix === "bind") {
this.helpers.add("bind");
name = _name;
value = `bind(ctx, ${value || undefined})`;
} else {
throw new Error("Invalid prop suffix");
}
}
name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
return `${name}: ${value || undefined}`;
}
formatPropObject(obj: { [prop: string]: any }): string {
const params = [];
for (const [n, v] of Object.entries(obj)) {
params.push(this.formatProp(n, v));
}
return params.join(", ");
}
compileComponent(ast: ASTComponent, ctx: Context) { compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx; let { block } = ctx;
// props // props
const hasSlotsProp = "slots" in ast.props;
const props: string[] = []; const props: string[] = [];
let hasSlotsProp = false; const propExpr = this.formatPropObject(ast.props);
for (let p in ast.props) { if (propExpr) {
const propName = /^[a-z_]+$/i.test(p) ? p : `'${p}'`; props.push(propExpr);
props.push(`${propName}: ${this.captureExpression(ast.props[p]) || undefined}`);
if (p === "slots") {
hasSlotsProp = true;
}
} }
// slots // slots
@@ -980,63 +1103,51 @@ export class CodeGenerator {
let ctxStr = "ctx"; let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) { if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx"); ctxStr = this.generateId("ctx");
this.helpers.add("capture");
this.addLine(`const ${ctxStr} = capture(ctx);`); this.addLine(`const ${ctxStr} = capture(ctx);`);
} }
let slotStr: string[] = []; let slotStr: string[] = [];
const initialTarget = this.target;
for (let slotName in ast.slots) { for (let slotName in ast.slots) {
let name = this.generateId("slot"); const slotAst = ast.slots[slotName].content;
const slot = new CodeTarget(name); const name = this.compileInNewTarget("slot", slotAst, ctx);
this.functions.push(slot);
this.target = slot;
const subCtx: Context = createContext(ctx);
this.compileAST(ast.slots[slotName].content, subCtx);
const params = [`__render: ${name}, __ctx: ${ctxStr}`]; const params = [`__render: ${name}, __ctx: ${ctxStr}`];
const scope = ast.slots[slotName].scope; const scope = ast.slots[slotName].scope;
if (scope) { if (scope) {
params.push(`__scope: "${scope}"`); params.push(`__scope: "${scope}"`);
} }
if (ast.slots[slotName].attrs) { if (ast.slots[slotName].attrs) {
for (const [n, v] of Object.entries(ast.slots[slotName].attrs!)) { params.push(this.formatPropObject(ast.slots[slotName].attrs!));
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
} }
const slotInfo = `{${params.join(", ")}}`; const slotInfo = `{${params.join(", ")}}`;
if (this.hasRef) { slotStr.push(`'${slotName}': ${slotInfo}`);
slot.code.unshift(` const refs = ctx.__owl__.refs`);
slotStr.push(`'${slotName}': ${slotInfo}`);
} else {
slotStr.push(`'${slotName}': ${slotInfo}`);
}
} }
this.target = initialTarget;
slotDef = `{${slotStr.join(", ")}}`; slotDef = `{${slotStr.join(", ")}}`;
} }
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) { if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
props.push(`slots: ${slotDef}`); this.helpers.add("markRaw");
props.push(`slots: markRaw(${slotDef})`);
} }
const propStr = `{${props.join(",")}}`; const propStr = `{${props.join(",")}}`;
let propString = propStr; let propString = propStr;
if (ast.dynamicProps) { if (ast.dynamicProps) {
if (!props.length) { propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}${
propString = `${compileExpr(ast.dynamicProps)}`; props.length ? ", " + propStr : ""
} else { })`;
propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
}
} }
let propVar: string; let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) { if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = this.generateId("props"); propVar = this.generateId("props");
this.addLine(`const ${propVar!} = ${propString}`); this.addLine(`const ${propVar!} = ${propString};`);
propString = propVar!; propString = propVar!;
} }
if (slotDef && (ast.dynamicProps || hasSlotsProp)) { if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
this.addLine(`${propVar!}.slots = Object.assign(${slotDef}, ${propVar!}.slots)`); this.helpers.add("markRaw");
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotDef}, ${propVar!}.slots))`);
} }
// cmap key // cmap key
@@ -1050,7 +1161,7 @@ export class CodeGenerator {
} }
if (this.dev) { if (this.dev) {
this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx)`); this.addLine(`helpers.validateProps(${expr}, ${propVar!}, ctx);`);
} }
if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) { if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
@@ -1072,6 +1183,7 @@ export class CodeGenerator {
} }
compileTSlot(ast: ASTSlot, ctx: Context) { compileTSlot(ast: ASTSlot, ctx: Context) {
this.helpers.add("callSlot");
let { block } = ctx; let { block } = ctx;
let blockString: string; let blockString: string;
let slotName; let slotName;
@@ -1083,24 +1195,9 @@ export class CodeGenerator {
slotName = "'" + ast.name + "'"; slotName = "'" + ast.name + "'";
} }
let scope = null; const scope = ast.attrs ? `{${this.formatPropObject(ast.attrs)}}` : null;
if (ast.attrs) {
const params = [];
for (const [n, v] of Object.entries(ast.attrs!)) {
params.push(`${n}: ${compileExpr(v) || undefined}`);
}
scope = `{${params.join(", ")}}`;
}
if (ast.defaultContent) { if (ast.defaultContent) {
let name = this.generateId("defaultContent"); const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
const slot = new CodeTarget(name);
this.functions.push(slot);
const initialTarget = this.target;
const subCtx: Context = createContext(ctx);
this.target = slot;
this.compileAST(ast.defaultContent, subCtx);
this.target = initialTarget;
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`; blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
} else { } else {
if (dynamic) { if (dynamic) {
@@ -1123,4 +1220,23 @@ export class CodeGenerator {
this.compileAST(ast.content, Object.assign({}, ctx, { translate: false })); this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
} }
} }
compileTPortal(ast: ASTTPortal, ctx: Context) {
this.helpers.add("Portal");
let { block } = ctx;
const name = this.compileInNewTarget("slot", ast.content, ctx);
const key = this.generateComponentKey();
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
this.helpers.add("capture");
this.addLine(`const ${ctxStr} = capture(ctx);`);
}
const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
if (block) {
this.insertAnchor(block);
}
block = this.createBlock(block, "multi", ctx);
this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
}
} }
+5 -5
View File
@@ -9,8 +9,10 @@ export type TemplateFunction = (blocks: any, utils: any) => Template;
interface CompileOptions extends Config { interface CompileOptions extends Config {
name?: string; name?: string;
} }
let nextId = 1; export function compile(
export function compile(template: string | Node, options: CompileOptions = {}): TemplateFunction { template: string | Element,
options: CompileOptions = {}
): TemplateFunction {
// parsing // parsing
const ast = parse(template); const ast = parse(template);
@@ -19,12 +21,10 @@ export function compile(template: string | Node, options: CompileOptions = {}):
template instanceof Node template instanceof Node
? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null ? !(template instanceof Element) || template.querySelector("[t-set], [t-call]") === null
: !template.includes("t-set") && !template.includes("t-call"); : !template.includes("t-set") && !template.includes("t-call");
const name = options.name || `template_${nextId++}`;
// code generation // code generation
const codeGenerator = new CodeGenerator(name, ast, { ...options, hasSafeContext }); const codeGenerator = new CodeGenerator(ast, { ...options, hasSafeContext });
const code = codeGenerator.generateCode(); const code = codeGenerator.generateCode();
// template function // template function
return new Function("bdom, helpers", code) as TemplateFunction; return new Function("bdom, helpers", code) as TemplateFunction;
} }
+3 -1
View File
@@ -329,7 +329,9 @@ export function compileExprToArray(expr: string): Token[] {
// Mark all variables that have been used locally. // Mark all variables that have been used locally.
// This assumes the expression has only one scope (incorrect but "good enough for now") // This assumes the expression has only one scope (incorrect but "good enough for now")
for (const token of tokens) { for (const token of tokens) {
if (token.type === "SYMBOL" && localVars.has(token.value)) { if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
token.originalValue = token.value;
token.value = `_${token.value}`;
token.isLocal = true; token.isLocal = true;
} }
} }
+120 -130
View File
@@ -20,6 +20,7 @@ export const enum ASTType {
TSlot, TSlot,
TCallBlock, TCallBlock,
TTranslation, TTranslation,
TPortal,
} }
export interface ASTText { export interface ASTText {
@@ -32,6 +33,17 @@ export interface ASTComment {
value: string; value: string;
} }
interface TModelInfo {
hasDynamicChildren?: boolean;
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
}
export interface ASTDomNode { export interface ASTDomNode {
type: ASTType.DomNode; type: ASTType.DomNode;
tag: string; tag: string;
@@ -40,15 +52,7 @@ export interface ASTDomNode {
content: AST[]; content: AST[];
ref: string | null; ref: string | null;
on: { [key: string]: string }; on: { [key: string]: string };
model: { model?: TModelInfo | null;
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
} | null;
ns: string | null; ns: string | null;
} }
@@ -92,8 +96,6 @@ export interface ASTTForEach {
key: string | null; key: string | null;
body: AST; body: AST;
memo: string; memo: string;
isOnlyChild: boolean;
hasNoComponent: boolean;
hasNoFirst: boolean; hasNoFirst: boolean;
hasNoLast: boolean; hasNoLast: boolean;
hasNoIndex: boolean; hasNoIndex: boolean;
@@ -149,6 +151,12 @@ export interface ASTTranslation {
content: AST | null; content: AST | null;
} }
export interface ASTTPortal {
type: ASTType.TPortal;
target: string;
content: AST;
}
export type AST = export type AST =
| ASTText | ASTText
| ASTComment | ASTComment
@@ -166,28 +174,41 @@ export type AST =
| ASTTCallBlock | ASTTCallBlock
| ASTLog | ASTLog
| ASTDebug | ASTDebug
| ASTTranslation; | ASTTranslation
| ASTTPortal;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Parser // Parser
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
interface ParsingContext { const cache: WeakMap<Element, AST> = new WeakMap();
inPreTag: boolean;
inSVG: boolean;
}
export function parse(xml: string | Node): AST { export function parse(xml: string | Element): AST {
const node = xml instanceof Element ? xml : (parseXML(`<t>${xml}</t>`).firstChild! as Element); if (typeof xml === "string") {
normalizeXML(node); const elem = parseXML(`<t>${xml}</t>`).firstChild as Element;
const ctx = { inPreTag: false, inSVG: false }; return _parse(elem);
const ast = parseNode(node, ctx); }
let ast = cache.get(xml);
if (!ast) { if (!ast) {
return { type: ASTType.Text, value: "" }; // we clone here the xml to prevent modifying it in place
ast = _parse(xml.cloneNode(true) as Element);
cache.set(xml, ast);
} }
return ast; return ast;
} }
function parseNode(node: ChildNode, ctx: ParsingContext): AST | null { function _parse(xml: Element): AST {
normalizeXML(xml);
const ctx = { inPreTag: false, inSVG: false };
return parseNode(xml, ctx) || { type: ASTType.Text, value: "" };
}
interface ParsingContext {
tModelInfo?: TModelInfo | null;
inPreTag: boolean;
inSVG: boolean;
}
function parseNode(node: Node, ctx: ParsingContext): AST | null {
if (!(node instanceof Element)) { if (!(node instanceof Element)) {
return parseTextCommentNode(node, ctx); return parseTextCommentNode(node, ctx);
} }
@@ -195,6 +216,7 @@ function parseNode(node: ChildNode, ctx: ParsingContext): AST | null {
parseTDebugLog(node, ctx) || parseTDebugLog(node, ctx) ||
parseTForEach(node, ctx) || parseTForEach(node, ctx) ||
parseTIf(node, ctx) || parseTIf(node, ctx) ||
parseTPortal(node, ctx) ||
parseTCall(node, ctx) || parseTCall(node, ctx) ||
parseTCallBlock(node, ctx) || parseTCallBlock(node, ctx) ||
parseTEscNode(node, ctx) || parseTEscNode(node, ctx) ||
@@ -217,24 +239,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
if (node.tagName !== "t") { if (node.tagName !== "t") {
return null; return null;
} }
const children: AST[] = []; return parseChildNodes(node, ctx);
for (let child of node.childNodes) {
const ast = parseNode(child, ctx);
if (ast) {
children.push(ast);
}
}
switch (children.length) {
case 0:
return null;
case 1:
return children[0];
default:
return {
type: ASTType.Multi,
content: children,
};
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -243,7 +248,7 @@ function parseTNode(node: Element, ctx: ParsingContext): AST | null {
const lineBreakRE = /[\r\n]/; const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g; const whitespaceRE = /\s+/g;
function parseTextCommentNode(node: ChildNode, ctx: ParsingContext): AST | null { function parseTextCommentNode(node: Node, ctx: ParsingContext): AST | null {
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
let value = node.textContent || ""; let value = node.textContent || "";
if (!ctx.inPreTag) { if (!ctx.inPreTag) {
@@ -291,6 +296,8 @@ function parseTDebugLog(node: Element, ctx: ParsingContext): AST | null {
const hasDotAtTheEnd = /\.[\w_]+\s*$/; const hasDotAtTheEnd = /\.[\w_]+\s*$/;
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/; const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null { function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
const { tagName } = node; const { tagName } = node;
const dynamicTag = node.getAttribute("t-tag"); const dynamicTag = node.getAttribute("t-tag");
@@ -298,27 +305,23 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (tagName === "t" && !dynamicTag) { if (tagName === "t" && !dynamicTag) {
return null; return null;
} }
const children: AST[] = []; if (tagName.startsWith("block-")) {
throw new Error(`Invalid tag name: '${tagName}'`);
}
ctx = Object.assign({}, ctx);
if (tagName === "pre") { if (tagName === "pre") {
ctx.inPreTag = true; ctx.inPreTag = true;
} }
const shouldAddSVGNS = tagName === "svg" || (tagName === "g" && !ctx.inSVG); const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
ctx.inSVG = ctx.inSVG || shouldAddSVGNS; ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null; const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
const ref = node.getAttribute("t-ref"); const ref = node.getAttribute("t-ref");
node.removeAttribute("t-ref"); node.removeAttribute("t-ref");
for (let child of node.childNodes) {
const ast = parseNode(child, ctx);
if (ast) {
children.push(ast);
}
}
const nodeAttrsNames = node.getAttributeNames(); const nodeAttrsNames = node.getAttributeNames();
const attrs: ASTDomNode["attrs"] = {}; const attrs: ASTDomNode["attrs"] = {};
const on: ASTDomNode["on"] = {}; const on: ASTDomNode["on"] = {};
let model: ASTDomNode["model"] = null; let model: TModelInfo | null = null;
for (let attr of nodeAttrsNames) { for (let attr of nodeAttrsNames) {
const value = node.getAttribute(attr)!; const value = node.getAttribute(attr)!;
@@ -366,16 +369,26 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
shouldTrim: hasTrimMod && (isOtherInput || isTextarea), shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea), shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
}; };
} else { if (isSelect) {
// don't pollute the original ctx
ctx = Object.assign({}, ctx);
ctx.tModelInfo = model;
}
} else if (attr.startsWith("block-")) {
throw new Error(`Invalid attribute: '${attr}'`);
} else if (attr !== "t-name") {
if (attr.startsWith("t-") && !attr.startsWith("t-att")) { if (attr.startsWith("t-") && !attr.startsWith("t-att")) {
throw new Error(`Unknown QWeb directive: '${attr}'`); throw new Error(`Unknown QWeb directive: '${attr}'`);
} }
const tModel = ctx.tModelInfo;
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
tModel.hasDynamicChildren = true;
}
attrs[attr] = value; attrs[attr] = value;
} }
} }
if (children.length === 1 && children[0].type === ASTType.TForEach) {
children[0].isOnlyChild = true; const children = parseChildren(node, ctx);
}
return { return {
type: ASTType.DomNode, type: ASTType.DomNode,
tag: tagName, tag: tagName,
@@ -500,8 +513,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
body, body,
memo, memo,
key, key,
isOnlyChild: false,
hasNoComponent: hasNoComponent(body),
hasNoFirst, hasNoFirst,
hasNoLast, hasNoLast,
hasNoIndex, hasNoIndex,
@@ -509,58 +520,6 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
}; };
} }
/**
* @returns true if we are sure the ast does not contain any component
*/
function hasNoComponent(ast: AST): boolean {
switch (ast.type) {
case ASTType.TComponent:
case ASTType.TOut:
case ASTType.TCall:
case ASTType.TCallBlock:
case ASTType.TSlot:
return false;
case ASTType.TSet:
case ASTType.Text:
case ASTType.Comment:
case ASTType.TEsc:
return true;
case ASTType.TKey:
return hasNoComponent(ast.content);
case ASTType.TDebug:
case ASTType.TLog:
case ASTType.TTranslation:
return ast.content ? hasNoComponent(ast.content) : true;
case ASTType.TForEach:
return ast.hasNoComponent;
case ASTType.Multi:
case ASTType.DomNode: {
for (let elem of ast.content) {
if (!hasNoComponent(elem)) {
return false;
}
}
return true;
}
case ASTType.TIf: {
if (!hasNoComponent(ast.content)) {
return false;
}
if (ast.tElif) {
for (let elem of ast.tElif) {
if (!hasNoComponent(elem.content)) {
return false;
}
}
}
if (ast.tElse && !hasNoComponent(ast.tElse)) {
return false;
}
return true;
}
}
}
function parseTKey(node: Element, ctx: ParsingContext): AST | null { function parseTKey(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-key")) { if (!node.hasAttribute("t-key")) {
return null; return null;
@@ -599,13 +558,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
}; };
} }
} }
const body: AST[] = []; const body = parseChildren(node, ctx);
for (let child of node.childNodes) {
const ast = parseNode(child, ctx);
if (ast) {
body.push(ast);
}
}
return { return {
type: ASTType.TCall, type: ASTType.TCall,
@@ -639,10 +592,7 @@ function parseTIf(node: Element, ctx: ParsingContext): AST | null {
} }
const condition = node.getAttribute("t-if")!; const condition = node.getAttribute("t-if")!;
node.removeAttribute("t-if"); node.removeAttribute("t-if");
const content = parseNode(node, ctx); const content = parseNode(node, ctx) || { type: ASTType.Text, value: "" };
if (!content) {
throw new Error("hmmm");
}
let nextElement = node.nextElementSibling; let nextElement = node.nextElementSibling;
// t-elifs // t-elifs
@@ -689,13 +639,7 @@ function parseTSetNode(node: Element, ctx: ParsingContext): AST | null {
const defaultValue = node.innerHTML === node.textContent ? node.textContent || null : null; const defaultValue = node.innerHTML === node.textContent ? node.textContent || null : null;
let body: AST[] | null = null; let body: AST[] | null = null;
if (node.textContent !== node.innerHTML) { if (node.textContent !== node.innerHTML) {
body = []; body = parseChildren(node, ctx);
for (let child of node.childNodes) {
let childAst = parseNode(child, ctx);
if (childAst) {
body.push(childAst);
}
}
} }
return { type: ASTType.TSet, name, value, defaultValue, body }; return { type: ASTType.TSet, name, value, defaultValue, body };
} }
@@ -738,6 +682,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const dynamicProps = node.getAttribute("t-props"); const dynamicProps = node.getAttribute("t-props");
node.removeAttribute("t-props"); node.removeAttribute("t-props");
const defaultSlotScope = node.getAttribute("t-slot-scope");
node.removeAttribute("t-slot-scope");
const props: ASTComponent["props"] = {}; const props: ASTComponent["props"] = {};
for (let name of node.getAttributeNames()) { for (let name of node.getAttributeNames()) {
const value = node.getAttribute(name)!; const value = node.getAttribute(name)!;
@@ -803,6 +750,9 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const defaultContent = parseChildNodes(clone, ctx); const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) { if (defaultContent) {
slots.default = { content: defaultContent }; slots.default = { content: defaultContent };
if (defaultSlotScope) {
slots.default.scope = defaultSlotScope;
}
} }
} }
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots }; return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots };
@@ -842,18 +792,58 @@ function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
}; };
} }
// -----------------------------------------------------------------------------
// Portal
// -----------------------------------------------------------------------------
function parseTPortal(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-portal")) {
return null;
}
const target = node.getAttribute("t-portal")!;
node.removeAttribute("t-portal");
const content = parseNode(node, ctx);
if (!content) {
return {
type: ASTType.Text,
value: "",
};
}
return {
type: ASTType.TPortal,
target,
content,
};
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// helpers // helpers
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function parseChildNodes(node: Element, ctx: ParsingContext): AST | null { /**
* Parse all the child nodes of a given node and return a list of ast elements
*/
function parseChildren(node: Element, ctx: ParsingContext): AST[] {
const children: AST[] = []; const children: AST[] = [];
for (let child of node.childNodes) { for (let child of node.childNodes) {
const childAst = parseNode(child, ctx); const childAst = parseNode(child, ctx);
if (childAst) { if (childAst) {
children.push(childAst); if (childAst.type === ASTType.Multi) {
children.push(...childAst.content);
} else {
children.push(childAst);
}
} }
} }
return children;
}
/**
* Parse all the child nodes of a given node and return an ast if possible.
* In the case there are multiple children, they are wrapped in a astmulti.
*/
function parseChildNodes(node: Element, ctx: ParsingContext): AST | null {
const children = parseChildren(node, ctx);
switch (children.length) { switch (children.length) {
case 0: case 0:
return null; return null;
+23 -13
View File
@@ -1,32 +1,42 @@
import type { Env } from "../app/app";
import type { ComponentNode } from "./component_node"; import type { ComponentNode } from "./component_node";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Component Class // Component Class
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export class Component { type Props = { [key: string]: any };
static template: string = "";
static style: string = "";
static props?: any;
props: any; interface StaticComponentProperties {
template: string;
defaultProps?: any;
props?: any;
}
export type ComponentConstructor<P extends Props = any, E = any> = (new (
props: P,
env: E,
node: ComponentNode
) => Component<P, E>) &
StaticComponentProperties;
export class Component<Props = any, Env = any> {
static template: string = "";
static props?: any;
static defaultProps?: any;
props: Props;
env: Env; env: Env;
__owl__: ComponentNode; __owl__: ComponentNode;
constructor(props: any, env: Env, node: ComponentNode) { constructor(props: Props, env: Env, node: ComponentNode) {
this.props = props; this.props = props;
this.env = env; this.env = env;
this.__owl__ = node; this.__owl__ = node;
} }
get el(): HTMLElement | Text | undefined {
const node = this.__owl__;
return node.bdom ? (node.bdom.firstNode() as any) : undefined;
}
setup() {} setup() {}
render(): Promise<void> { render(deep: boolean = false) {
return this.__owl__.render(); this.__owl__.render(deep);
} }
} }
+195 -83
View File
@@ -1,6 +1,16 @@
import type { App, Env } from "../app/app"; import type { App, Env } from "../app/app";
import { BDom, VNode } from "../blockdom"; import { BDom, VNode } from "../blockdom";
import { Component } from "./component"; import {
clearReactivesForCallback,
Reactive,
reactive,
TARGET,
NonReactive,
getSubscriptions,
} from "../reactivity";
import { batched, Callback } from "../utils";
import { Component, ComponentConstructor } from "./component";
import { fibersInError, handleError } from "./error_handling";
import { import {
Fiber, Fiber,
makeChildFiber, makeChildFiber,
@@ -8,20 +18,78 @@ import {
MountFiber, MountFiber,
MountOptions, MountOptions,
RootFiber, RootFiber,
__internal__destroyed,
} from "./fibers"; } from "./fibers";
import { handleError, fibersInError } from "./error_handling";
import { applyDefaultProps } from "./props_validation"; import { applyDefaultProps } from "./props_validation";
import { STATUS } from "./status"; import { STATUS } from "./status";
import { applyStyles } from "./style";
export function component( let currentNode: ComponentNode | null = null;
name: string | typeof Component,
props: any, export function getCurrent(): ComponentNode {
if (!currentNode) {
throw new Error("No active component (a hook function should only be called in 'setup')");
}
return currentNode;
}
export function useComponent(): Component {
return currentNode!.component;
}
// -----------------------------------------------------------------------------
// Integration with reactivity system (useState)
// -----------------------------------------------------------------------------
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/**
* Creates a reactive object that will be observed by the current component.
* Reading data from the returned object (eg during rendering) will cause the
* component to subscribe to that data and be rerendered when it changes.
*
* @param state the state to observe
* @returns a reactive object that will cause the component to re-render on
* relevant changes
* @see reactive
*/
export function useState<T extends object>(state: T): Reactive<T> | NonReactive<T> {
const node = getCurrent();
let render = batchedRenderFunctions.get(node)!;
if (!render) {
render = batched(node.render.bind(node));
batchedRenderFunctions.set(node, render);
// manual implementation of onWillDestroy to break cyclic dependency
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
if (node.app.dev) {
Object.defineProperty(node, "subscriptions", {
get() {
return getSubscriptions(render);
},
});
}
}
return reactive(state, render);
}
// -----------------------------------------------------------------------------
// component function (used in compiled template code)
// -----------------------------------------------------------------------------
type Props = { [key: string]: any };
function arePropsDifferent(props1: Props, props2: Props): boolean {
for (let k in props1) {
if (props1[k] !== props2[k]) {
return true;
}
}
return Object.keys(props1).length !== Object.keys(props2).length;
}
export function component<P extends object>(
name: string | ComponentConstructor<P>,
props: P,
key: string, key: string,
ctx: ComponentNode, ctx: ComponentNode,
parent: any parent: any
): ComponentNode { ): ComponentNode<P> {
let node: any = ctx.children[key]; let node: any = ctx.children[key];
let isDynamic = typeof name !== "string"; let isDynamic = typeof name !== "string";
@@ -39,7 +107,16 @@ export function component(
const parentFiber = ctx.fiber!; const parentFiber = ctx.fiber!;
if (node) { if (node) {
node.updateAndRender(props, parentFiber); let shouldRender = node.forceNextRender;
if (shouldRender) {
node.forceNextRender = false;
} else {
const currentProps = node.component.props[TARGET];
shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props);
}
if (shouldRender) {
node.updateAndRender(props, parentFiber);
}
} else { } else {
// new component // new component
let C; let C;
@@ -54,33 +131,25 @@ export function component(
node = new ComponentNode(C, props, ctx.app, ctx); node = new ComponentNode(C, props, ctx.app, ctx);
ctx.children[key] = node; ctx.children[key] = node;
const fiber = makeChildFiber(node, parentFiber); node.initiateRender(new Fiber(node, parentFiber));
node.initiateRender(fiber);
} }
return node; return node;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Component VNode // Component VNode class
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
let currentNode: ComponentNode | null = null;
export function getCurrent(): ComponentNode | null {
return currentNode;
}
type LifecycleHook = Function; type LifecycleHook = Function;
export class ComponentNode<T extends typeof Component = typeof Component> export class ComponentNode<P extends object = any, E = any> implements VNode<ComponentNode<P, E>> {
implements VNode<ComponentNode>
{
el?: HTMLElement | Text | undefined; el?: HTMLElement | Text | undefined;
app: App; app: App;
fiber: Fiber | null = null; fiber: Fiber | null = null;
component: InstanceType<T>; component: Component<P, E>;
bdom: BDom | null = null; bdom: BDom | null = null;
status: STATUS = STATUS.NEW; status: STATUS = STATUS.NEW;
forceNextRender: boolean = false;
renderFn: Function; renderFn: Function;
parent: ComponentNode | null; parent: ComponentNode | null;
@@ -95,9 +164,9 @@ export class ComponentNode<T extends typeof Component = typeof Component>
mounted: LifecycleHook[] = []; mounted: LifecycleHook[] = [];
willPatch: LifecycleHook[] = []; willPatch: LifecycleHook[] = [];
patched: LifecycleHook[] = []; patched: LifecycleHook[] = [];
destroyed: LifecycleHook[] = []; willDestroy: LifecycleHook[] = [];
constructor(C: T, props: any, app: App, parent?: ComponentNode) { constructor(C: ComponentConstructor<P, E>, props: P, app: App, parent?: ComponentNode) {
currentNode = this; currentNode = this;
this.app = app; this.app = app;
this.parent = parent || null; this.parent = parent || null;
@@ -105,25 +174,23 @@ export class ComponentNode<T extends typeof Component = typeof Component>
applyDefaultProps(props, C); applyDefaultProps(props, C);
const env = (parent && parent.childEnv) || app.env; const env = (parent && parent.childEnv) || app.env;
this.childEnv = env; this.childEnv = env;
props = useState(props);
this.component = new C(props, env, this) as any; this.component = new C(props, env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this); this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
if (C.style) {
applyStyles(C);
}
this.component.setup(); this.component.setup();
currentNode = null;
} }
mountComponent(target: any, options?: MountOptions): Promise<InstanceType<T>> { mountComponent(target: any, options?: MountOptions) {
const fiber = new MountFiber(this, target, options); const fiber = new MountFiber(this, target, options);
this.app.scheduler.addFiber(fiber); this.app.scheduler.addFiber(fiber);
this.initiateRender(fiber); this.initiateRender(fiber);
return fiber.promise.then(() => this.component);
} }
async initiateRender(fiber: Fiber | MountFiber) { async initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber; this.fiber = fiber;
if (this.mounted.length) { if (this.mounted.length) {
fiber.root.mounted.push(fiber); fiber.root!.mounted.push(fiber);
} }
const component = this.component; const component = this.component;
try { try {
@@ -137,16 +204,29 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
} }
async render() { async render(deep: boolean = false) {
const current = this.fiber; let current = this.fiber;
if (current && !current.bdom && !fibersInError.has(current)) { if (current && current.root!.locked) {
return current.root.promise; await Promise.resolve();
// situation may have changed after the microtask tick
current = this.fiber;
} }
if (!this.bdom && !current) { if (current) {
// should find a way to return the future mounting promise if (!current.bdom && !fibersInError.has(current)) {
if (deep) {
// we want the render from this point on to be with deep=true
current.deep = deep;
}
return;
}
// if current rendering was with deep=true, we want this one to be the same
deep = deep || current.deep;
} else if (!this.bdom) {
return; return;
} }
const fiber = makeRootFiber(this); const fiber = makeRootFiber(this);
fiber.deep = deep;
this.fiber = fiber; this.fiber = fiber;
this.app.scheduler.addFiber(fiber); this.app.scheduler.addFiber(fiber);
await Promise.resolve(); await Promise.resolve();
@@ -164,71 +244,93 @@ export class ComponentNode<T extends typeof Component = typeof Component>
// a root fiber to a child fiber in the previous microtick, because it was // a root fiber to a child fiber in the previous microtick, because it was
// embedded in a rendering coming from above, so the fiber will be rendered // embedded in a rendering coming from above, so the fiber will be rendered
// in the next microtick anyway, so we should not render it again. // in the next microtick anyway, so we should not render it again.
if (this.fiber && (current || !fiber.parent)) { if (this.fiber === fiber && (current || !fiber.parent)) {
this._render(fiber); this._render(fiber);
} }
return fiber.root.promise;
} }
_render(fiber: Fiber | RootFiber) { _render(fiber: Fiber | RootFiber) {
try { try {
fiber.bdom = this.renderFn(); fiber.bdom = this.renderFn();
fiber.root.counter--; fiber.root!.counter--;
} catch (e) { } catch (e) {
handleError({ node: this, error: e }); handleError({ node: this, error: e });
} }
} }
destroy() { destroy() {
if (this.status === STATUS.MOUNTED) { let shouldRemove = this.status === STATUS.MOUNTED;
callWillUnmount(this); this._destroy();
if (shouldRemove) {
this.bdom!.remove(); this.bdom!.remove();
} }
callDestroyed(this); }
function callWillUnmount(node: ComponentNode) { _destroy() {
const component = node.component; const component = this.component;
for (let cb of node.willUnmount) { if (this.status === STATUS.MOUNTED) {
cb.call(component); for (let cb of this.willUnmount) {
}
for (let child of Object.values(node.children)) {
if (child.status === STATUS.MOUNTED) {
callWillUnmount(child);
}
}
}
function callDestroyed(node: ComponentNode) {
const component = node.component;
node.status = STATUS.DESTROYED;
for (let child of Object.values(node.children)) {
callDestroyed(child);
}
for (let cb of node.destroyed) {
cb.call(component); cb.call(component);
} }
} }
for (let child of Object.values(this.children)) {
child._destroy();
}
for (let cb of this.willDestroy) {
cb.call(component);
}
this.status = STATUS.DESTROYED;
} }
async updateAndRender(props: any, parentFiber: Fiber) { async updateAndRender(props: any, parentFiber: Fiber) {
// update // update
const fiber = makeChildFiber(this, parentFiber); const fiber = makeChildFiber(this, parentFiber);
this.fiber = fiber; this.fiber = fiber;
if (this.willPatch.length) {
parentFiber.root.willPatch.push(fiber);
}
if (this.patched.length) {
parentFiber.root.patched.push(fiber);
}
const component = this.component; const component = this.component;
applyDefaultProps(props, component.constructor as any); applyDefaultProps(props, component.constructor as any);
currentNode = this;
props = useState(props);
currentNode = null;
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props))); const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom; await prom;
if (fiber !== this.fiber) { if (fiber !== this.fiber) {
return; return;
} }
this.component.props = props; component.props = props;
this._render(fiber); this._render(fiber);
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
}
}
/**
* Finds a child that has dom that is not yet updated, and update it. This
* method is meant to be used only in the context of repatching the dom after
* a mounted hook failed and was handled.
*/
updateDom() {
if (!this.fiber) {
return;
}
if (this.bdom === this.fiber!.bdom) {
// If the error was handled by some child component, we need to find it to
// apply its change
for (let k in this.children) {
const child = this.children[k];
child.updateDom();
}
} else {
// if we get here, this is the component that handled the error and rerendered
// itself, so we can simply patch the dom
this.bdom!.patch(this.fiber!.bdom, false);
this.fiber!.appliedToDom = true;
this.fiber = null;
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -254,32 +356,42 @@ export class ComponentNode<T extends typeof Component = typeof Component>
} }
patch() { patch() {
this.bdom!.patch(this!.fiber!.bdom!, false); if (this.fiber && this.fiber.parent) {
// we only patch here renderings coming from above. renderings initiated
// by the component will be patched independently in the appropriate
// fiber.complete
this._patch();
}
}
_patch() {
const hasChildren = Object.keys(this.children).length > 0;
this.bdom!.patch(this!.fiber!.bdom!, hasChildren);
if (hasChildren) {
this.cleanOutdatedChildren();
}
this.fiber!.appliedToDom = true; this.fiber!.appliedToDom = true;
this.fiber = null; this.fiber = null;
} }
beforeRemove() { beforeRemove() {
visitRemovedNodes(this); this._destroy();
} }
remove() { remove() {
this.bdom!.remove(); this.bdom!.remove();
} }
}
function visitRemovedNodes(node: ComponentNode) { cleanOutdatedChildren() {
if (node.status === STATUS.MOUNTED) { const children = this.children;
const component = node.component; for (const key in children) {
for (let cb of node.willUnmount) { const node = children[key];
cb.call(component); const status = node.status;
if (status !== STATUS.MOUNTED) {
delete children[key];
if (status !== STATUS.DESTROYED) {
node.destroy();
}
}
} }
} }
for (let child of Object.values(node.children)) {
visitRemovedNodes(child);
}
node.status = STATUS.DESTROYED;
if (node.destroyed.length) {
__internal__destroyed.push(node);
}
} }
+25 -16
View File
@@ -16,27 +16,26 @@ function _handleError(node: ComponentNode | null, error: any, isFirstRound = fal
const errorHandlers = nodeErrorHandlers.get(node); const errorHandlers = nodeErrorHandlers.get(node);
if (errorHandlers) { if (errorHandlers) {
if (isFirstRound && fiber) { let stopped = false;
fiber.root.counter--; // execute in the opposite order
} for (let i = errorHandlers.length - 1; i >= 0; i--) {
let propagate = true;
for (const h of errorHandlers) {
try { try {
h(error); errorHandlers[i](error);
propagate = false; stopped = true;
break;
} catch (e) { } catch (e) {
error = e; error = e;
} }
} }
if (propagate) { if (stopped) {
return _handleError(node.parent, error); if (isFirstRound && fiber && fiber.node.fiber) {
fiber.root!.counter--;
}
return true;
} }
return true;
} else {
return _handleError(node.parent, error);
} }
return _handleError(node.parent, error);
} }
type ErrorParams = { error: any } & ({ node: ComponentNode } | { fiber: Fiber }); type ErrorParams = { error: any } & ({ node: ComponentNode } | { fiber: Fiber });
@@ -45,13 +44,23 @@ export function handleError(params: ErrorParams) {
const node = "node" in params ? params.node : params.fiber.node; const node = "node" in params ? params.node : params.fiber.node;
const fiber = "fiber" in params ? params.fiber : node.fiber!; const fiber = "fiber" in params ? params.fiber : node.fiber!;
fibersInError.set(fiber.root, error); // resets the fibers on components if possible. This is important so that
// new renderings can be properly included in the initial one, if any.
let current: Fiber | null = fiber;
do {
current.node.fiber = current;
current = current.parent;
} while (current);
fibersInError.set(fiber.root!, error);
const handled = _handleError(node, error, true); const handled = _handleError(node, error, true);
if (!handled) { if (!handled) {
console.warn(`[Owl] Unhandled error. Destroying the root component`);
try { try {
node.app.destroy(); node.app.destroy();
} catch (e) {} } catch (e) {
console.error(e);
}
} }
return handled;
} }
+52 -62
View File
@@ -1,20 +1,13 @@
import type { BDom } from "../blockdom"; import { BDom, mount } from "../blockdom";
import { mount } from "../blockdom";
import type { ComponentNode } from "./component_node"; import type { ComponentNode } from "./component_node";
import { STATUS } from "./status";
import { fibersInError, handleError } from "./error_handling"; import { fibersInError, handleError } from "./error_handling";
import { STATUS } from "./status";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber { export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
let current = node.fiber; let current = node.fiber;
if (current) { if (current) {
// current is necessarily a rootfiber here cancelFibers(current.children);
let root = parent.root; current.root = null;
cancelFibers(root, current.children);
current.children = [];
current.parent = parent;
root.counter++;
current.root = root;
return current;
} }
return new Fiber(node, parent); return new Fiber(node, parent);
} }
@@ -22,40 +15,46 @@ export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
export function makeRootFiber(node: ComponentNode): Fiber { export function makeRootFiber(node: ComponentNode): Fiber {
let current = node.fiber; let current = node.fiber;
if (current) { if (current) {
let root = current.root; let root = current.root!;
root.counter -= cancelFibers(root, current.children); root.counter = root.counter + 1 - cancelFibers(current.children);
current.children = []; current.children = [];
root.counter++;
current.bdom = null; current.bdom = null;
if (fibersInError.has(current)) { if (fibersInError.has(current)) {
fibersInError.delete(current); fibersInError.delete(current);
fibersInError.delete(root); fibersInError.delete(root);
current.appliedToDom = false;
} }
return current; return current;
} }
const fiber = new RootFiber(node); const fiber = new RootFiber(node, null);
if (node.willPatch.length) { if (node.willPatch.length) {
fiber.willPatch.push(fiber); fiber.willPatch.push(fiber);
} }
if (node.patched.length) { if (node.patched.length) {
fiber.patched.push(fiber); fiber.patched.push(fiber);
} }
return fiber; return fiber;
} }
/** /**
* @returns number of not-yet rendered fibers cancelled * @returns number of not-yet rendered fibers cancelled
*/ */
function cancelFibers(root: any, fibers: Fiber[]): number { function cancelFibers(fibers: Fiber[]): number {
let result = 0; let result = 0;
for (let fiber of fibers) { for (let fiber of fibers) {
fiber.node.fiber = null; fiber.node.fiber = null;
fiber.root = root; if (fiber.bdom) {
if (!fiber.bdom) { // if fiber has been rendered, this means that the component props have
// been updated. however, this fiber will not be patched to the dom, so
// it could happen that the next render compare the current props with
// the same props, and skip the render completely. With the next line,
// we kindly request the component code to force a render, so it works as
// expected.
fiber.node.forceNextRender = true;
} else {
result++; result++;
} }
result += cancelFibers(root, fiber.children); result += cancelFibers(fiber.children);
} }
return result; return result;
} }
@@ -63,16 +62,18 @@ function cancelFibers(root: any, fibers: Fiber[]): number {
export class Fiber { export class Fiber {
node: ComponentNode; node: ComponentNode;
bdom: BDom | null = null; bdom: BDom | null = null;
root: RootFiber; root: RootFiber | null; // A Fiber that has been replaced by another has no root
parent: Fiber | null; parent: Fiber | null;
children: Fiber[] = []; children: Fiber[] = [];
appliedToDom = false; appliedToDom = false;
deep: boolean = false;
constructor(node: ComponentNode, parent: Fiber | null) { constructor(node: ComponentNode, parent: Fiber | null) {
this.node = node; this.node = node;
this.parent = parent; this.parent = parent;
if (parent) { if (parent) {
const root = parent.root; this.deep = parent.deep;
const root = parent.root!;
root.counter++; root.counter++;
this.root = root; this.root = root;
parent.children.push(this); parent.children.push(this);
@@ -84,27 +85,18 @@ export class Fiber {
export class RootFiber extends Fiber { export class RootFiber extends Fiber {
counter: number = 1; counter: number = 1;
resolve: any;
promise: Promise<any>;
reject: any;
// only add stuff in this if they have registered some hooks // only add stuff in this if they have registered some hooks
willPatch: Fiber[] = []; willPatch: Fiber[] = [];
patched: Fiber[] = []; patched: Fiber[] = [];
mounted: Fiber[] = []; mounted: Fiber[] = [];
// A fiber is typically locked when it is completing and the patch has not, or is being applied.
constructor(node: ComponentNode) { // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
super(node, null); locked: boolean = false;
this.counter = 1;
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
complete() { complete() {
const node = this.node; const node = this.node;
this.locked = true;
let current: Fiber | undefined = undefined; let current: Fiber | undefined = undefined;
try { try {
// Step 1: calling all willPatch lifecycle hooks // Step 1: calling all willPatch lifecycle hooks
@@ -123,16 +115,8 @@ export class RootFiber extends Fiber {
current = undefined; current = undefined;
// Step 2: patching the dom // Step 2: patching the dom
node.bdom!.patch(this.bdom!, Object.keys(node.children).length > 0); node._patch();
this.appliedToDom = true; this.locked = false;
// Step 3: calling all destroyed hooks
for (let node of __internal__destroyed) {
for (let cb of node.destroyed) {
cb();
}
}
__internal__destroyed.length = 0;
// Step 4: calling all mounted lifecycle hooks // Step 4: calling all mounted lifecycle hooks
let mountedFibers = this.mounted; let mountedFibers = this.mounted;
@@ -155,18 +139,13 @@ export class RootFiber extends Fiber {
} }
} }
} }
// unregistering the fiber
node.fiber = null;
} catch (e) { } catch (e) {
if (!handleError({ fiber: current || this, error: e })) { this.locked = false;
this.reject(e); handleError({ fiber: current || this, error: e });
}
} }
} }
} }
export let __internal__destroyed: ComponentNode[] = [];
type Position = "first-child" | "last-child"; type Position = "first-child" | "last-child";
export interface MountOptions { export interface MountOptions {
@@ -178,7 +157,7 @@ export class MountFiber extends RootFiber {
position: Position; position: Position;
constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) { constructor(node: ComponentNode, target: HTMLElement, options: MountOptions = {}) {
super(node); super(node, null);
this.target = target; this.target = target;
this.position = options.position || "last-child"; this.position = options.position || "last-child";
} }
@@ -186,13 +165,27 @@ export class MountFiber extends RootFiber {
let current: Fiber | undefined = this; let current: Fiber | undefined = this;
try { try {
const node = this.node; const node = this.node;
node.bdom = this.bdom; (node.app.constructor as any).validateTarget(this.target);
if (this.position === "last-child" || this.target.childNodes.length === 0) { if (node.bdom) {
mount(node.bdom!, this.target); // this is a complicated situation: if we mount a fiber with an existing
// bdom, this means that this same fiber was already completed, mounted,
// but a crash occurred in some mounted hook. Then, it was handled and
// the new rendering is being applied.
node.updateDom();
} else { } else {
const firstChild = this.target.childNodes[0]; node.bdom = this.bdom;
mount(node.bdom!, this.target, firstChild); if (this.position === "last-child" || this.target.childNodes.length === 0) {
mount(node.bdom!, this.target);
} else {
const firstChild = this.target.childNodes[0];
mount(node.bdom!, this.target, firstChild);
}
} }
// unregistering the fiber before mounted since it can do another render
// and that the current rendering is obviously completed
node.fiber = null;
node.status = STATUS.MOUNTED; node.status = STATUS.MOUNTED;
this.appliedToDom = true; this.appliedToDom = true;
let mountedFibers = this.mounted; let mountedFibers = this.mounted;
@@ -203,11 +196,8 @@ export class MountFiber extends RootFiber {
} }
} }
} }
node.fiber = null;
} catch (e) { } catch (e) {
if (!handleError({ fiber: current as Fiber, error: e })) { handleError({ fiber: current as Fiber, error: e });
this.reject(e);
}
} }
} }
} }
+9 -1
View File
@@ -1,4 +1,5 @@
import { filterOutModifiersFromData } from "../blockdom/config"; import { filterOutModifiersFromData } from "../blockdom/config";
import { STATUS } from "./status";
export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget | null) => { export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarget | null) => {
const { data: _data, modifiers } = filterOutModifiersFromData(data); const { data: _data, modifiers } = filterOutModifiersFromData(data);
@@ -30,7 +31,14 @@ export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarg
// We check this rather than data[0] being truthy (or typeof function) so that it crashes // We check this rather than data[0] being truthy (or typeof function) so that it crashes
// as expected when there is a handler expression that evaluates to a falsy value // as expected when there is a handler expression that evaluates to a falsy value
if (Object.hasOwnProperty.call(data, 0)) { if (Object.hasOwnProperty.call(data, 0)) {
data[0].call(data[1] ? data[1].__owl__.component : null, ev); const handler = data[0];
if (typeof handler !== "function") {
throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
}
let node = data[1] ? data[1].__owl__ : null;
if (node ? node.status === STATUS.MOUNTED : true) {
handler.call(node ? node.component : null, ev);
}
} }
return stopped; return stopped;
}; };
+63 -29
View File
@@ -1,72 +1,106 @@
import { getCurrent } from "./component_node"; import { getCurrent } from "./component_node";
import { nodeErrorHandlers } from "./error_handling"; import { nodeErrorHandlers } from "./error_handling";
function wrapError(fn: (...args: any[]) => any, hookName: string) {
const error = new Error(`The following error occurred in ${hookName}: `) as Error & {
cause: any;
};
return (...args: any[]) => {
try {
const result = fn(...args);
if (result instanceof Promise) {
return result.catch((cause) => {
error.cause = cause;
if (cause instanceof Error) {
error.message += `"${cause.message}"`;
}
throw error;
});
}
return result;
} catch (cause) {
if (cause instanceof Error) {
error.message += `"${cause.message}"`;
}
throw error;
}
};
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// hooks // hooks
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export function onWillStart(fn: () => Promise<void> | void | any) { export function onWillStart(fn: () => Promise<void> | void | any) {
const node = getCurrent()!; const node = getCurrent();
node.willStart.push(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
} }
export function onWillUpdateProps(fn: (nextProps: any) => Promise<void> | void | any) { export function onWillUpdateProps(fn: (nextProps: any) => Promise<void> | void | any) {
const node = getCurrent()!; const node = getCurrent();
node.willUpdateProps.push(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
} }
export function onMounted(fn: () => void | any) { export function onMounted(fn: () => void | any) {
const node = getCurrent()!; const node = getCurrent();
node.mounted.push(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
} }
export function onWillPatch(fn: () => Promise<void> | any | void) { export function onWillPatch(fn: () => Promise<void> | any | void) {
const node = getCurrent()!; const node = getCurrent();
node.willPatch.unshift(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
} }
export function onPatched(fn: () => void | any) { export function onPatched(fn: () => void | any) {
const node = getCurrent()!; const node = getCurrent();
node.patched.push(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.patched.push(decorate(fn.bind(node.component), "onPatched"));
} }
export function onWillUnmount(fn: () => Promise<void> | void | any) { export function onWillUnmount(fn: () => Promise<void> | void | any) {
const node = getCurrent()!; const node = getCurrent();
node.willUnmount.unshift(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
} }
export function onDestroyed(fn: () => Promise<void> | void | any) { export function onWillDestroy(fn: () => Promise<void> | void | any) {
const node = getCurrent()!; const node = getCurrent();
node.destroyed.push(fn); const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
} }
export function onWillRender(fn: () => void | any) { export function onWillRender(fn: () => void | any) {
const node = getCurrent()!; const node = getCurrent();
const renderFn = node.renderFn; const renderFn = node.renderFn;
node.renderFn = () => { const decorate = node.app.dev ? wrapError : (fn: any) => fn;
fn(); node.renderFn = decorate(() => {
fn.call(node.component);
return renderFn(); return renderFn();
}; }, "onWillRender");
} }
export function onRendered(fn: () => void | any) { export function onRendered(fn: () => void | any) {
const node = getCurrent()!; const node = getCurrent();
const renderFn = node.renderFn; const renderFn = node.renderFn;
node.renderFn = () => { const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.renderFn = decorate(() => {
const result = renderFn(); const result = renderFn();
fn(); fn.call(node.component);
return result; return result;
}; }, "onRendered");
} }
export function onError(fn: (error: Error) => void | any) { type OnErrorCallback = (error: any) => void | any;
const node = getCurrent()!; export function onError(callback: OnErrorCallback) {
const node = getCurrent();
let handlers = nodeErrorHandlers.get(node); let handlers = nodeErrorHandlers.get(node);
if (handlers) { if (!handlers) {
handlers.push(fn);
} else {
handlers = []; handlers = [];
handlers.push(fn);
nodeErrorHandlers.set(node, handlers); nodeErrorHandlers.set(node, handlers);
} }
handlers.push(callback.bind(node.component));
} }
+58 -45
View File
@@ -1,16 +1,16 @@
import { Component } from "./component"; import { ComponentConstructor } from "./component";
/** /**
* Apply default props (only top level). * Apply default props (only top level).
* *
* Note that this method does modify in place the props * Note that this method does modify in place the props
*/ */
export function applyDefaultProps(props: { [key: string]: any }, ComponentClass: typeof Component) { export function applyDefaultProps<P>(props: P, ComponentClass: ComponentConstructor<P>) {
const defaultProps = (ComponentClass as any).defaultProps; const defaultProps = ComponentClass.defaultProps;
if (defaultProps) { if (defaultProps) {
for (let propName in defaultProps) { for (let propName in defaultProps) {
if (props![propName] === undefined) { if ((props as any)[propName] === undefined) {
props![propName] = defaultProps[propName]; (props as any)[propName] = defaultProps[propName];
} }
} }
} }
@@ -19,6 +19,14 @@ export function applyDefaultProps(props: { [key: string]: any }, ComponentClass:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Prop validation helper // Prop validation helper
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
function getPropDescription(staticProps: any) {
if (staticProps instanceof Array) {
return Object.fromEntries(
staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true]))
);
}
return staticProps || { "*": true };
}
/** /**
* Validate the component props (or next props) against the (static) props * Validate the component props (or next props) against the (static) props
@@ -26,60 +34,65 @@ export function applyDefaultProps(props: { [key: string]: any }, ComponentClass:
* visit recursively the props and all the children to check if they are valid. * visit recursively the props and all the children to check if they are valid.
* This is why it is only done in 'dev' mode. * This is why it is only done in 'dev' mode.
*/ */
export const validateProps = function (name: string | typeof Component, props: any, parent?: any) { export function validateProps<P>(name: string | ComponentConstructor<P>, props: P, parent?: any) {
const ComponentClass = ( const ComponentClass =
typeof name !== "string" ? name : parent.constructor.components[name] typeof name !== "string"
) as typeof Component; ? name
: (parent.constructor.components[name] as ComponentConstructor<P> | undefined);
if (!ComponentClass) {
// this is an error, wrong component. We silently return here instead so the
// error is triggered by the usual path ('component' function)
return;
}
applyDefaultProps(props, ComponentClass); applyDefaultProps(props, ComponentClass);
const propsDef = ComponentClass.props; const defaultProps = ComponentClass.defaultProps || {};
if (propsDef instanceof Array) { let propsDef = getPropDescription(ComponentClass.props);
// list of strings (prop names) const allowAdditionalProps = "*" in propsDef;
for (const propName of propsDef) {
if (propName[propName.length - 1] === "?") { for (let propName in propsDef) {
// optional prop if (propName === "*") {
break; continue;
} }
if (!(propName in props)) { const propDef = propsDef[propName];
let isMandatory = !!propDef;
if (typeof propDef === "object" && "optional" in propDef) {
isMandatory = !propDef.optional;
}
if (isMandatory && propName in defaultProps) {
throw new Error(
`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`
);
}
if ((props as any)[propName] === undefined) {
if (isMandatory) {
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`); throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
} else {
continue;
} }
} }
for (let key in props) { let isValid;
if (!propsDef.includes(key) && !propsDef.includes(key + "?")) { try {
throw new Error(`Unknown prop '${key}' given to component '${ComponentClass.name}'`); isValid = isValidProp((props as any)[propName], propDef);
} } catch (e) {
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
(e as Error).message
})`;
throw e;
} }
} else if (propsDef) { if (!isValid) {
// propsDef is an object now throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
for (let propName in propsDef) {
if (props[propName] === undefined) {
if (propsDef[propName] && !propsDef[propName].optional) {
throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
} else {
continue;
}
}
let isValid;
try {
isValid = isValidProp(props[propName], propsDef[propName]);
} catch (e) {
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
(e as Error).message
})`;
throw e;
}
if (!isValid) {
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
}
} }
}
if (!allowAdditionalProps) {
for (let propName in props) { for (let propName in props) {
if (!(propName in propsDef)) { if (!(propName in propsDef)) {
throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`); throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
} }
} }
} }
}; }
/** /**
* Check if an invidual prop value matches its (static) prop definition * Check if an invidual prop value matches its (static) prop definition
+7 -12
View File
@@ -1,5 +1,5 @@
import { Fiber, RootFiber } from "./fibers";
import { fibersInError } from "./error_handling"; import { fibersInError } from "./error_handling";
import { Fiber, RootFiber } from "./fibers";
import { STATUS } from "./status"; import { STATUS } from "./status";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -7,12 +7,15 @@ import { STATUS } from "./status";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export class Scheduler { export class Scheduler {
// capture the value of requestAnimationFrame as soon as possible, to avoid
// interactions with other code, such as test frameworks that override them
static requestAnimationFrame = window.requestAnimationFrame.bind(window);
tasks: Set<RootFiber> = new Set(); tasks: Set<RootFiber> = new Set();
isRunning: boolean = false; isRunning: boolean = false;
requestAnimationFrame: Window["requestAnimationFrame"]; requestAnimationFrame: Window["requestAnimationFrame"];
constructor(requestAnimationFrame: Window["requestAnimationFrame"]) { constructor() {
this.requestAnimationFrame = requestAnimationFrame; this.requestAnimationFrame = Scheduler.requestAnimationFrame;
} }
start() { start() {
@@ -25,7 +28,7 @@ export class Scheduler {
} }
addFiber(fiber: Fiber) { addFiber(fiber: Fiber) {
this.tasks.add(fiber.root); this.tasks.add(fiber.root!);
if (!this.isRunning) { if (!this.isRunning) {
this.start(); this.start();
} }
@@ -38,19 +41,12 @@ export class Scheduler {
flush() { flush() {
this.tasks.forEach((fiber) => { this.tasks.forEach((fiber) => {
if (fiber.root !== fiber) { if (fiber.root !== fiber) {
// this is wrong! should be something like
// if (this.tasks.has(fiber.root)) {
// // parent rendering has completed
// fiber.resolve();
// this.tasks.delete(fiber);
// }
this.tasks.delete(fiber); this.tasks.delete(fiber);
return; return;
} }
const hasError = fibersInError.has(fiber); const hasError = fibersInError.has(fiber);
if (hasError && fiber.counter !== 0) { if (hasError && fiber.counter !== 0) {
this.tasks.delete(fiber); this.tasks.delete(fiber);
fiber.reject(fibersInError.get(fiber));
return; return;
} }
if (fiber.node.status === STATUS.DESTROYED) { if (fiber.node.status === STATUS.DESTROYED) {
@@ -61,7 +57,6 @@ export class Scheduler {
if (fiber.counter === 0) { if (fiber.counter === 0) {
if (!hasError) { if (!hasError) {
fiber.complete(); fiber.complete();
fiber.resolve();
} }
this.tasks.delete(fiber); this.tasks.delete(fiber);
} }
-88
View File
@@ -1,88 +0,0 @@
import { Component } from "./component";
export const globalStylesheets: { [key: string]: HTMLStyleElement } = {};
export function registerSheet(id: string, css: string) {
const sheet = document.createElement("style");
sheet.innerHTML = processSheet(css);
globalStylesheets[id] = sheet;
}
/**
* Apply the stylesheets defined by the component. Note that we need to make
* sure all inherited stylesheets are applied as well, in a reverse order to
* ensure that <style/> will be applied to the DOM in the order they are
* included in the document. We then delete the `style` key from the constructor
* to make sure we do not apply it again.
*/
export function applyStyles(ComponentClass: typeof Component) {
const toApply: [string, string][] = [];
while (ComponentClass && ComponentClass.style) {
if (ComponentClass.hasOwnProperty("style")) {
toApply.push([ComponentClass.style, ComponentClass.name]);
delete (ComponentClass as any).style;
}
ComponentClass = Object.getPrototypeOf(ComponentClass);
}
while (toApply.length) {
const [styleId, componentName] = toApply.pop()!;
activateSheet(styleId, componentName);
}
}
function activateSheet(id: string, name: string) {
const sheet = globalStylesheets[id];
if (!sheet) {
throw new Error(
`Invalid css stylesheet for component '${name}'. Did you forget to use the 'css' tag helper?`
);
}
sheet.dataset.component = name;
document.head.appendChild(sheet);
}
function processSheet(str: string): string {
const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
const selectorStack: string[][] = [];
const parts: string[] = [];
let rules: string[] = [];
function generateSelector(stackIndex: number, parentSelector?: string) {
const parts: string[] = [];
for (const selector of selectorStack[stackIndex]) {
let part = (parentSelector && parentSelector + " " + selector) || selector;
if (part.includes("&")) {
part = selector.replace(/&/g, parentSelector || "");
}
if (stackIndex < selectorStack.length - 1) {
part = generateSelector(stackIndex + 1, part);
}
parts.push(part);
}
return parts.join(", ");
}
function generateRules() {
if (rules.length) {
parts.push(generateSelector(0) + " {");
parts.push(...rules);
parts.push("}");
rules = [];
}
}
while (tokens.length) {
let token = tokens.shift()!;
if (token === "}") {
generateRules();
selectorStack.pop();
} else {
if (tokens[0] === "{") {
generateRules();
selectorStack.push(token.split(/\s*,\s*/));
tokens.shift();
}
if (tokens[0] === ";") {
rules.push(" " + token + ";");
}
}
}
return parts.join("\n");
}
+24 -17
View File
@@ -1,6 +1,6 @@
import type { Env } from "./app/app"; import type { Env } from "./app/app";
import { getCurrent } from "./component/component_node"; import { getCurrent } from "./component/component_node";
import { onMounted, onPatched, onWillPatch, onWillUnmount } from "./component/lifecycle_hooks"; import { onMounted, onPatched, onWillUnmount } from "./component/lifecycle_hooks";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// useRef // useRef
@@ -11,10 +11,11 @@ import { onMounted, onPatched, onWillPatch, onWillUnmount } from "./component/li
* html node or component. * html node or component.
*/ */
export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null } { export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el: T | null } {
const node = getCurrent()!; const node = getCurrent();
const refs = node.refs;
return { return {
get el(): T | null { get el(): T | null {
return node.refs[name] || null; return refs[name] || null;
}, },
}; };
} }
@@ -28,7 +29,13 @@ export function useRef<T extends HTMLElement = HTMLElement>(name: string): { el:
* need a reference to the env of the component calling them. * need a reference to the env of the component calling them.
*/ */
export function useEnv<E extends Env>(): E { export function useEnv<E extends Env>(): E {
return getCurrent()!.component.env as any; return getCurrent().component.env as any;
}
function extendEnv(currentEnv: Object, extension: Object): Object {
const env = Object.create(currentEnv);
const descrs = Object.getOwnPropertyDescriptors(extension);
return Object.freeze(Object.defineProperties(env, descrs));
} }
/** /**
@@ -37,10 +44,15 @@ export function useEnv<E extends Env>(): E {
* constructor method. * constructor method.
*/ */
export function useSubEnv(envExtension: Env) { export function useSubEnv(envExtension: Env) {
const node = getCurrent()!; const node = getCurrent();
node.childEnv = Object.freeze(Object.assign({}, node.childEnv, envExtension)); node.component.env = extendEnv(node.component.env as any, envExtension);
useChildSubEnv(envExtension);
} }
export function useChildSubEnv(envExtension: Env) {
const node = getCurrent();
node.childEnv = extendEnv(node.childEnv, envExtension);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// useEffect // useEffect
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -73,17 +85,12 @@ export function useEffect(effect: Effect, computeDependencies: () => any[] = ()
cleanup = effect(...dependencies) || NO_OP; cleanup = effect(...dependencies) || NO_OP;
}); });
let shouldReapplyOnPatch = false;
onWillPatch(() => {
const newDeps = computeDependencies();
shouldReapplyOnPatch = newDeps.some((val, i) => val !== dependencies[i]);
if (shouldReapplyOnPatch) {
cleanup();
dependencies = newDeps;
}
});
onPatched(() => { onPatched(() => {
if (shouldReapplyOnPatch) { const newDeps = computeDependencies();
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
if (shouldReapply) {
dependencies = newDeps;
cleanup();
cleanup = effect(...dependencies) || NO_OP; cleanup = effect(...dependencies) || NO_OP;
} }
}); });
@@ -114,7 +121,7 @@ export function useExternalListener(
handler: EventListener, handler: EventListener,
eventParams?: AddEventListenerOptions eventParams?: AddEventListenerOptions
) { ) {
const node = getCurrent()!; const node = getCurrent();
const boundHandler = handler.bind(node.component); const boundHandler = handler.bind(node.component);
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams)); onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams)); onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
+10 -29
View File
@@ -9,8 +9,10 @@ import {
remove, remove,
text, text,
toggler, toggler,
comment,
} from "./blockdom"; } from "./blockdom";
import { mainEventHandler } from "./component/handler"; import { mainEventHandler } from "./component/handler";
export type { Reactive } from "./reactivity";
config.shouldNormalizeDom = false; config.shouldNormalizeDom = false;
config.mainEventHandler = mainEventHandler; config.mainEventHandler = mainEventHandler;
@@ -28,37 +30,16 @@ export const blockDom = {
toggler, toggler,
createBlock, createBlock,
html, html,
comment,
}; };
import type { AppConfig } from "./app/app"; export { App, mount } from "./app/app";
import { App } from "./app/app"; export { Component } from "./component/component";
import { Component } from "./component/component"; export { useComponent, useState } from "./component/component_node";
import { getCurrent } from "./component/component_node";
export { App, Component };
export async function mount<T extends typeof Component>(
C: T,
target: HTMLElement,
config: AppConfig = {}
): Promise<InstanceType<T>> {
const app = new App(C);
return app.configure(config).mount(target);
}
export function useComponent(): Component {
const current = getCurrent();
return current!.component;
}
export { status } from "./component/status"; export { status } from "./component/status";
export { Portal } from "./portal"; export { reactive, markRaw, toRaw } from "./reactivity";
export { Memo } from "./memo"; export { useEffect, useEnv, useExternalListener, useRef, useChildSubEnv, useSubEnv } from "./hooks";
export { css, xml } from "./tags"; export { EventBus, whenReady, loadFile, markup, xml } from "./utils";
export { useState, reactive } from "./reactivity";
export { useEffect, useEnv, useExternalListener, useRef, useSubEnv } from "./hooks";
export { EventBus, whenReady, loadFile, markup } from "./utils";
export { export {
onWillStart, onWillStart,
onMounted, onMounted,
@@ -68,7 +49,7 @@ export {
onPatched, onPatched,
onWillRender, onWillRender,
onRendered, onRendered,
onDestroyed, onWillDestroy,
onError, onError,
} from "./component/lifecycle_hooks"; } from "./component/lifecycle_hooks";
-47
View File
@@ -1,47 +0,0 @@
import { Component } from "./component/component";
import type { ComponentNode } from "./component/component_node";
import { xml } from "./tags";
import { Fiber } from "./component/fibers";
export class Memo extends Component {
static template = xml`<t t-slot="default"/>`;
constructor(props: any, env: any, node: ComponentNode) {
super(props, env, node);
// prevent patching process conditionally
let applyPatch = false;
const patchFn = node.patch;
node.patch = () => {
if (applyPatch) {
patchFn.call(node);
applyPatch = false;
}
};
// check props change, and render/apply patch if it changed
let prevProps = props;
const updateAndRender = node.updateAndRender;
node.updateAndRender = function (props: any, parentFiber: Fiber) {
const shouldUpdate = !shallowEqual(prevProps, props);
if (shouldUpdate) {
prevProps = props;
updateAndRender.call(node, props, parentFiber);
applyPatch = true;
}
return Promise.resolve();
};
}
}
/**
* we assume that each object have the same set of keys
*/
function shallowEqual(p1: any, p2: any): boolean {
for (let k in p1) {
if (k !== "slots" && p1[k] !== p2[k]) {
return false;
}
}
return true;
}
+18 -13
View File
@@ -1,7 +1,7 @@
import type { ComponentNode } from "./component/component_node"; import { onWillUnmount } from "./component/lifecycle_hooks";
import { Component } from "./component/component"; import { xml } from "./utils";
import { xml } from "./tags";
import { BDom, text, VNode } from "./blockdom"; import { BDom, text, VNode } from "./blockdom";
import { Component } from "./component/component";
const VText: any = text("").constructor; const VText: any = text("").constructor;
@@ -35,9 +35,11 @@ class VPortal extends VText implements Partial<VNode<VPortal>> {
this.realBDom!.beforeRemove(); this.realBDom!.beforeRemove();
} }
remove() { remove() {
super.remove(); if (this.realBDom) {
this.realBDom!.remove(); super.remove();
this.realBDom = null; this.realBDom!.remove();
this.realBDom = null;
}
} }
patch(other: VPortal) { patch(other: VPortal) {
@@ -57,14 +59,17 @@ export class Portal extends Component {
target: { target: {
type: String, type: String,
}, },
slots: true,
}; };
constructor(props: any, env: any, node: ComponentNode) { setup() {
super(props, env, node); const node = this.__owl__;
node._render = function (fiber: any) { const renderFn = node.renderFn;
const bdom = new VPortal(props.target, this.renderFn()); node.renderFn = () => new VPortal(this.props.target, renderFn());
fiber.bdom = bdom; onWillUnmount(() => {
fiber.root.counter--; if (node.bdom) {
}; node.bdom.remove();
}
});
} }
} }
+294 -106
View File
@@ -1,18 +1,42 @@
import { onWillUnmount } from "./component/lifecycle_hooks"; import { Callback } from "./utils";
import { ComponentNode, getCurrent } from "./component/component_node";
// Allows to get the target of a Reactive (used for making a new Reactive from the underlying object) // Allows to get the target of a Reactive (used for making a new Reactive from the underlying object)
const TARGET = Symbol("Target"); export const TARGET = Symbol("Target");
// Escape hatch to prevent reactivity system to turn something into a reactive
const SKIP = Symbol("Skip");
// Special key to subscribe to, to be notified of key creation/deletion // Special key to subscribe to, to be notified of key creation/deletion
const KEYCHANGES = Symbol("Key changes"); const KEYCHANGES = Symbol("Key changes");
type ObjectKey = string | number | symbol;
type Target = object; type Target = object;
type Callback = () => void; type Collection = Set<any> | Map<any, any> | WeakMap<any, any>;
type Reactive<T extends Target = Target> = T & { type CollectionRawType = "Set" | "Map" | "WeakMap";
export type Reactive<T extends Target = Target> = T & {
[TARGET]: any; [TARGET]: any;
}; };
export type NonReactive<T extends Target = Target> = T & {
[SKIP]: any;
};
const objectToString = Object.prototype.toString;
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
/**
* extract "RawType" from strings like "[object RawType]" => this lets us ignore
* many native objects such as Promise (whose toString is [object Promise])
* or Date ([object Date]), while also supporting collections without using
* instanceof in a loop
*
* @param obj the object to check
* @returns the raw type of the object
*/
function rawType(obj: any) {
return objectToString.call(obj).slice(8, -1);
}
/** /**
* Checks whether a given value can be made into a reactive object. * Checks whether a given value can be made into a reactive object.
* *
@@ -20,17 +44,44 @@ type Reactive<T extends Target = Target> = T & {
* @returns whether the value can be made reactive * @returns whether the value can be made reactive
*/ */
function canBeMadeReactive(value: any): boolean { function canBeMadeReactive(value: any): boolean {
return ( if (typeof value !== "object") {
typeof value === "object" && return false;
value !== null && }
!(value instanceof Date) && return SUPPORTED_RAW_TYPES.has(rawType(value));
!(value instanceof Promise) && }
!(value instanceof String) && /**
!(value instanceof Number) * Creates a reactive from the given object/callback if possible and returns it,
); * returns the original object otherwise.
*
* @param value the value make reactive
* @returns a reactive for the given object when possible, the original otherwise
*/
function possiblyReactive(val: any, cb: Callback) {
return canBeMadeReactive(val) ? reactive(val, cb) : val;
} }
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>(); /**
* Mark an object or array so that it is ignored by the reactivity system
*
* @param value the value to mark
* @returns the object itself
*/
export function markRaw<T extends Target>(value: T): NonReactive<T> {
(value as any)[SKIP] = true;
return value as NonReactive<T>;
}
/**
* Given a reactive objet, return the raw (non reactive) underlying object
*
* @param value a reactive value
* @returns the underlying value
*/
export function toRaw<T extends object>(value: Reactive<T>): T {
return value[TARGET] || value;
}
const targetToKeysToCallbacks = new WeakMap<Target, Map<PropertyKey, Set<Callback>>>();
/** /**
* Observes a given key on a target with an callback. The callback will be * Observes a given key on a target with an callback. The callback will be
* called when the given key changes on the target. * called when the given key changes on the target.
@@ -40,7 +91,7 @@ const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>
* or deletion) * or deletion)
* @param callback the function to call when the key changes * @param callback the function to call when the key changes
*/ */
function observeTargetKey(target: Target, key: ObjectKey, callback: Callback): void { function observeTargetKey(target: Target, key: PropertyKey, callback: Callback): void {
if (!targetToKeysToCallbacks.get(target)) { if (!targetToKeysToCallbacks.get(target)) {
targetToKeysToCallbacks.set(target, new Map()); targetToKeysToCallbacks.set(target, new Map());
} }
@@ -49,6 +100,10 @@ function observeTargetKey(target: Target, key: ObjectKey, callback: Callback): v
keyToCallbacks.set(key, new Set()); keyToCallbacks.set(key, new Set());
} }
keyToCallbacks.get(key)!.add(callback); keyToCallbacks.get(key)!.add(callback);
if (!callbacksToTargets.has(callback)) {
callbacksToTargets.set(callback, new Set());
}
callbacksToTargets.get(callback)!.add(target);
} }
/** /**
* Notify Reactives that are observing a given target that a key has changed on * Notify Reactives that are observing a given target that a key has changed on
@@ -59,7 +114,7 @@ function observeTargetKey(target: Target, key: ObjectKey, callback: Callback): v
* @param key the key that changed (or Symbol `KEYCHANGES` if a key was created * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
* or deleted) * or deleted)
*/ */
function notifyReactives(target: Target, key: ObjectKey): void { function notifyReactives(target: Target, key: PropertyKey): void {
const keyToCallbacks = targetToKeysToCallbacks.get(target); const keyToCallbacks = targetToKeysToCallbacks.get(target);
if (!keyToCallbacks) { if (!keyToCallbacks) {
return; return;
@@ -81,7 +136,7 @@ const callbacksToTargets = new WeakMap<Callback, Set<Target>>();
* *
* @param callback the callback for which the reactives need to be cleared * @param callback the callback for which the reactives need to be cleared
*/ */
function clearReactivesForCallback(callback: Callback): void { export function clearReactivesForCallback(callback: Callback): void {
const targetsToClear = callbacksToTargets.get(callback); const targetsToClear = callbacksToTargets.get(callback);
if (!targetsToClear) { if (!targetsToClear) {
return; return;
@@ -95,9 +150,21 @@ function clearReactivesForCallback(callback: Callback): void {
callbacks.delete(callback); callbacks.delete(callback);
} }
} }
targetsToClear.clear();
} }
const reactiveCache = new WeakMap<Target, Map<Callback, Reactive>>(); export function getSubscriptions(callback: Callback) {
const targets = callbacksToTargets.get(callback) || [];
return [...targets].map((target) => {
const keysToCallbacks = targetToKeysToCallbacks.get(target);
return {
target,
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
};
});
}
const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive>>();
/** /**
* Creates a reactive proxy for an object. Reading data on the reactive object * Creates a reactive proxy for an object. Reading data on the reactive object
* subscribes to changes to the data. Writing data on the object will cause the * subscribes to changes to the data. Writing data on the object will cause the
@@ -125,10 +192,16 @@ const reactiveCache = new WeakMap<Target, Map<Callback, Reactive>>();
* reactive has changed * reactive has changed
* @returns a proxy that tracks changes to it * @returns a proxy that tracks changes to it
*/ */
export function reactive<T extends Target>(target: T, callback: Callback): Reactive<T> { export function reactive<T extends Target>(
target: T,
callback: Callback = () => {}
): Reactive<T> | NonReactive<T> {
if (!canBeMadeReactive(target)) { if (!canBeMadeReactive(target)) {
throw new Error(`Cannot make the given value reactive`); throw new Error(`Cannot make the given value reactive`);
} }
if (SKIP in target) {
return target as NonReactive<T>;
}
const originalTarget = (target as Reactive)[TARGET]; const originalTarget = (target as Reactive)[TARGET];
if (originalTarget) { if (originalTarget) {
return reactive(originalTarget, callback); return reactive(originalTarget, callback);
@@ -138,103 +211,218 @@ export function reactive<T extends Target>(target: T, callback: Callback): React
} }
const reactivesForTarget = reactiveCache.get(target)!; const reactivesForTarget = reactiveCache.get(target)!;
if (!reactivesForTarget.has(callback)) { if (!reactivesForTarget.has(callback)) {
const proxy = new Proxy(target, { const targetRawType = rawType(target);
get(target: any, key: ObjectKey, proxy: Reactive<T>) { const handler = COLLECTION_RAWTYPES.has(targetRawType)
if (key === TARGET) { ? collectionsProxyHandler(target as Collection, callback, targetRawType as CollectionRawType)
return target; : basicProxyHandler<T>(callback);
} const proxy = new Proxy(target, handler as ProxyHandler<T>) as Reactive<T>;
observeTargetKey(target, key, callback);
const value = Reflect.get(target, key, proxy);
if (!canBeMadeReactive(value)) {
return value;
}
return reactive(value, callback);
},
set(target, key, value, proxy) {
const isNewKey = !Object.hasOwnProperty.call(target, key);
const originalValue = Reflect.get(target, key, proxy);
const ret = Reflect.set(target, key, value, proxy);
if (isNewKey) {
notifyReactives(target, KEYCHANGES);
}
// While Array length may trigger the set trap, it's not actually set by this
// method but is updated behind the scenes, and the trap is not called with the
// new value. We disable the "same-value-optimization" for it because of that.
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
notifyReactives(target, key);
}
return ret;
},
deleteProperty(target, key) {
const ret = Reflect.deleteProperty(target, key);
notifyReactives(target, KEYCHANGES);
notifyReactives(target, key);
return ret;
},
ownKeys(target) {
observeTargetKey(target, KEYCHANGES, callback);
return Reflect.ownKeys(target);
},
has(target, key) {
// TODO: this observes all key changes instead of only the presence of the argument key
observeTargetKey(target, KEYCHANGES, callback);
return Reflect.has(target, key);
},
});
reactivesForTarget.set(callback, proxy); reactivesForTarget.set(callback, proxy);
if (!callbacksToTargets.has(callback)) {
callbacksToTargets.set(callback, new Set());
}
callbacksToTargets.get(callback)!.add(target);
} }
return reactivesForTarget.get(callback) as Reactive<T>; return reactivesForTarget.get(callback) as Reactive<T>;
} }
/** /**
* Creates a batched version of a callback so that all calls to it in the same * Creates a basic proxy handler for regular objects and arrays.
* microtick will only call the original callback once.
* *
* @param callback the callback to batch * @param callback @see reactive
* @returns a batched version of the original callback * @returns a proxy handler object
*/ */
export function batched(callback: Callback): Callback { function basicProxyHandler<T extends Target>(callback: Callback): ProxyHandler<T> {
let called = false; return {
return async () => { get(target: any, key: PropertyKey, proxy: Reactive<T>) {
// This await blocks all calls to the callback here, then releases them sequentially if (key === TARGET) {
// in the next microtick. This line decides the granularity of the batch. return target;
await Promise.resolve(); }
if (!called) { observeTargetKey(target, key, callback);
called = true; return possiblyReactive(Reflect.get(target, key, proxy), callback);
callback(); },
// wait for all calls in this microtick to fall through before resetting "called" set(target, key, value, proxy) {
// so that only the first call to the batched function calls the original callback const isNewKey = !objectHasOwnProperty.call(target, key);
await Promise.resolve(); const originalValue = Reflect.get(target, key, proxy);
called = false; const ret = Reflect.set(target, key, value, proxy);
if (isNewKey) {
notifyReactives(target, KEYCHANGES);
}
// While Array length may trigger the set trap, it's not actually set by this
// method but is updated behind the scenes, and the trap is not called with the
// new value. We disable the "same-value-optimization" for it because of that.
if (originalValue !== value || (Array.isArray(target) && key === "length")) {
notifyReactives(target, key);
}
return ret;
},
deleteProperty(target, key) {
const ret = Reflect.deleteProperty(target, key);
// TODO: only notify when something was actually deleted
notifyReactives(target, KEYCHANGES);
notifyReactives(target, key);
return ret;
},
ownKeys(target) {
observeTargetKey(target, KEYCHANGES, callback);
return Reflect.ownKeys(target);
},
has(target, key) {
// TODO: this observes all key changes instead of only the presence of the argument key
// observing the key itself would observe value changes instead of presence changes
// so we may need a finer grained system to distinguish observing value vs presence.
observeTargetKey(target, KEYCHANGES, callback);
return Reflect.has(target, key);
},
} as ProxyHandler<T>;
}
/**
* Creates a function that will observe the key that is passed to it when called
* and delegates to the underlying method.
*
* @param methodName name of the method to delegate to
* @param target @see reactive
* @param callback @see reactive
*/
function makeKeyObserver(methodName: "has" | "get", target: any, callback: Callback) {
return (key: any) => {
key = toRaw(key);
observeTargetKey(target, key, callback);
return possiblyReactive(target[methodName](key), callback);
};
}
/**
* Creates an iterable that will delegate to the underlying iteration method and
* observe keys as necessary.
*
* @param methodName name of the method to delegate to
* @param target @see reactive
* @param callback @see reactive
*/
function makeIteratorObserver(
methodName: "keys" | "values" | "entries" | typeof Symbol.iterator,
target: any,
callback: Callback
) {
return function* () {
observeTargetKey(target, KEYCHANGES, callback);
const keys = target.keys();
for (const item of target[methodName]()) {
const key = keys.next().value;
observeTargetKey(target, key, callback);
yield possiblyReactive(item, callback);
} }
}; };
} }
const batchedRenderFunctions = new WeakMap<ComponentNode, Callback>();
/** /**
* Creates a reactive object that will be observed by the current component. * Creates a function that will delegate to an underlying method, and check if
* Reading data from the returned object (eg during rendering) will cause the * that method has modified the presence or value of a key, and notify the
* component to subscribe to that data and be rerendered when it changes. * reactives appropriately.
* *
* @param state the state to observe * @param setterName name of the method to delegate to
* @returns a reactive object that will cause the component to re-render on * @param getterName name of the method which should be used to retrieve the
* relevant changes * value before calling the delegate method for comparison purposes
* @see reactive * @param target @see reactive
*/ */
export function useState<T extends object>(state: T): Reactive<T> { function delegateAndNotify(
const node = getCurrent()!; setterName: "set" | "add" | "delete",
if (!batchedRenderFunctions.has(node)) { getterName: "has" | "get",
batchedRenderFunctions.set( target: any
node, ) {
batched(() => node.render()) return (key: any, value: any) => {
); key = toRaw(key);
} const hadKey = target.has(key);
const render = batchedRenderFunctions.get(node)!; const originalValue = target[getterName](key);
const reactiveState = reactive(state, render); const ret = target[setterName](key, value);
onWillUnmount(() => clearReactivesForCallback(render)); const hasKey = target.has(key);
return reactiveState; if (hadKey !== hasKey) {
notifyReactives(target, KEYCHANGES);
}
if (originalValue !== value) {
notifyReactives(target, key);
}
return ret;
};
}
/**
* Creates a function that will clear the underlying collection and notify that
* the keys of the collection have changed.
*
* @param target @see reactive
*/
function makeClearNotifier(target: Map<any, any> | Set<any>) {
return () => {
const allKeys = [...target.keys()];
target.clear();
notifyReactives(target, KEYCHANGES);
for (const key of allKeys) {
notifyReactives(target, key);
}
};
}
/**
* Maps raw type of an object to an object containing functions that can be used
* to build an appropritate proxy handler for that raw type. Eg: when making a
* reactive set, calling the has method should mark the key that is being
* retrieved as observed, and calling the add or delete method should notify the
* reactives that the key which is being added or deleted has been modified.
*/
const rawTypeToFuncHandlers = {
Set: (target: any, callback: Callback) => ({
has: makeKeyObserver("has", target, callback),
add: delegateAndNotify("add", "has", target),
delete: delegateAndNotify("delete", "has", target),
keys: makeIteratorObserver("keys", target, callback),
values: makeIteratorObserver("values", target, callback),
entries: makeIteratorObserver("entries", target, callback),
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
clear: makeClearNotifier(target),
get size() {
observeTargetKey(target, KEYCHANGES, callback);
return target.size;
},
}),
Map: (target: any, callback: Callback) => ({
has: makeKeyObserver("has", target, callback),
get: makeKeyObserver("get", target, callback),
set: delegateAndNotify("set", "get", target),
delete: delegateAndNotify("delete", "has", target),
keys: makeIteratorObserver("keys", target, callback),
values: makeIteratorObserver("values", target, callback),
entries: makeIteratorObserver("entries", target, callback),
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
clear: makeClearNotifier(target),
get size() {
observeTargetKey(target, KEYCHANGES, callback);
return target.size;
},
}),
WeakMap: (target: any, callback: Callback) => ({
has: makeKeyObserver("has", target, callback),
get: makeKeyObserver("get", target, callback),
set: delegateAndNotify("set", "get", target),
delete: delegateAndNotify("delete", "has", target),
}),
};
/**
* Creates a proxy handler for collections (Set/Map/WeakMap)
*
* @param callback @see reactive
* @param target @see reactive
* @returns a proxy handler object
*/
function collectionsProxyHandler<T extends Collection>(
target: T,
callback: Callback,
targetRawType: CollectionRawType
): ProxyHandler<T> {
// TODO: if performance is an issue we can create the special handlers lazily when each
// property is read.
const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
return Object.assign(basicProxyHandler(callback), {
get(target: any, key: PropertyKey) {
if (key === TARGET) {
return target;
}
if (objectHasOwnProperty.call(specialHandlers, key)) {
return (specialHandlers as any)[key];
}
observeTargetKey(target, key, callback);
return possiblyReactive(target[key], callback);
},
}) as ProxyHandler<T>;
} }
-28
View File
@@ -1,28 +0,0 @@
import { registerSheet } from "./component/style";
import { globalTemplates } from "./app/template_set";
// -----------------------------------------------------------------------------
// Global templates
// -----------------------------------------------------------------------------
export function xml(...args: Parameters<typeof String.raw>) {
const name = `__template__${xml.nextId++}`;
const value = String.raw(...args);
globalTemplates[name] = value;
return name;
}
xml.nextId = 1;
// -----------------------------------------------------------------------------
// Global stylesheets
// -----------------------------------------------------------------------------
export function css(strings: TemplateStringsArray, ...args: any[]) {
const name = `__sheet__${css.nextId++}`;
const value = String.raw(strings, ...args);
registerSheet(name, value);
return name;
}
css.nextId = 1;
+49
View File
@@ -1,3 +1,38 @@
export type Callback = () => void;
/**
* Creates a batched version of a callback so that all calls to it in the same
* microtick will only call the original callback once.
*
* @param callback the callback to batch
* @returns a batched version of the original callback
*/
export function batched(callback: Callback): Callback {
let called = false;
return async () => {
// This await blocks all calls to the callback here, then releases them sequentially
// in the next microtick. This line decides the granularity of the batch.
await Promise.resolve();
if (!called) {
called = true;
callback();
// wait for all calls in this microtick to fall through before resetting "called"
// so that only the first call to the batched function calls the original callback
await Promise.resolve();
called = false;
}
};
}
export function validateTarget(target: HTMLElement) {
if (!(target instanceof HTMLElement)) {
throw new Error("Cannot mount component: the target is not a valid DOM element");
}
if (!document.body.contains(target)) {
throw new Error("Cannot mount a component on a detached dom node");
}
}
export class EventBus extends EventTarget { export class EventBus extends EventTarget {
trigger(name: string, payload?: any) { trigger(name: string, payload?: any) {
this.dispatchEvent(new CustomEvent(name, { detail: payload })); this.dispatchEvent(new CustomEvent(name, { detail: payload }));
@@ -36,3 +71,17 @@ export class Markup extends String {}
export function markup(value: any) { export function markup(value: any) {
return new Markup(value); return new Markup(value);
} }
// -----------------------------------------------------------------------------
// xml tag helper
// -----------------------------------------------------------------------------
export const globalTemplates: { [key: string]: string | Element } = {};
export function xml(...args: Parameters<typeof String.raw>) {
const name = `__template__${xml.nextId++}`;
const value = String.raw(...args);
globalTemplates[name] = value;
return name;
}
xml.nextId = 1;
+108 -125
View File
@@ -49,14 +49,16 @@ exports[`Reactivity: useState concurrent renderings 3`] = `
exports[`Reactivity: useState destroyed component before being mounted is inactive 1`] = ` exports[`Reactivity: useState destroyed component before being mounted is inactive 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].a; let b2;
return block1([d1]); if (ctx['state'].flag) {
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
return block1([], [b2]);
} }
}" }"
`; `;
@@ -64,17 +66,13 @@ exports[`Reactivity: useState destroyed component before being mounted is inacti
exports[`Reactivity: useState destroyed component before being mounted is inactive 2`] = ` exports[`Reactivity: useState destroyed component before being mounted is inactive 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2; let txt1 = ctx['contextObj'].a;
if (ctx['state'].flag) { return block1([txt1]);
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
return block1([], [b2]);
} }
}" }"
`; `;
@@ -82,23 +80,7 @@ exports[`Reactivity: useState destroyed component before being mounted is inacti
exports[`Reactivity: useState destroyed component is inactive 1`] = ` exports[`Reactivity: useState destroyed component is inactive 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].a;
return block1([d1]);
}
}"
`;
exports[`Reactivity: useState destroyed component is inactive 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -112,18 +94,31 @@ exports[`Reactivity: useState destroyed component is inactive 2`] = `
}" }"
`; `;
exports[`Reactivity: useState destroyed component is inactive 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['contextObj'].a;
return block1([txt1]);
}
}"
`;
exports[`Reactivity: useState one components can subscribe twice to same context 1`] = ` exports[`Reactivity: useState one components can subscribe twice to same context 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`); let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj1'].a; let txt1 = ctx['contextObj1'].a;
let d2 = ctx['contextObj2'].b; let txt2 = ctx['contextObj2'].b;
return block1([d1, d2]); return block1([txt1, txt2]);
} }
}" }"
`; `;
@@ -131,14 +126,14 @@ exports[`Reactivity: useState one components can subscribe twice to same context
exports[`Reactivity: useState parent and children subscribed to same context 1`] = ` exports[`Reactivity: useState parent and children subscribed to same context 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].a; let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return block1([d1]); let txt1 = ctx['contextObj'].b;
return block1([txt1], [b2]);
} }
}" }"
`; `;
@@ -146,15 +141,13 @@ exports[`Reactivity: useState parent and children subscribed to same context 1`]
exports[`Reactivity: useState parent and children subscribed to same context 2`] = ` exports[`Reactivity: useState parent and children subscribed to same context 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); let txt1 = ctx['contextObj'].a;
let d1 = ctx['contextObj'].b; return block1([txt1]);
return block1([d1], [b2]);
} }
}" }"
`; `;
@@ -225,14 +218,14 @@ exports[`Reactivity: useState several nodes on different level use same context
exports[`Reactivity: useState two components are updated in parallel 1`] = ` exports[`Reactivity: useState two components are updated in parallel 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].value; let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return block1([d1]); let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
return block1([], [b2, b3]);
} }
}" }"
`; `;
@@ -240,15 +233,13 @@ exports[`Reactivity: useState two components are updated in parallel 1`] = `
exports[`Reactivity: useState two components are updated in parallel 2`] = ` exports[`Reactivity: useState two components are updated in parallel 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx); let txt1 = ctx['contextObj'].value;
let b3 = component(\`Child\`, {}, key + \`__2\`, node, ctx); return block1([txt1]);
return block1([], [b2, b3]);
} }
}" }"
`; `;
@@ -256,23 +247,7 @@ exports[`Reactivity: useState two components are updated in parallel 2`] = `
exports[`Reactivity: useState two components can subscribe to same context 1`] = ` exports[`Reactivity: useState two components can subscribe to same context 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].value;
return block1([d1]);
}
}"
`;
exports[`Reactivity: useState two components can subscribe to same context 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
@@ -284,41 +259,24 @@ exports[`Reactivity: useState two components can subscribe to same context 2`] =
}" }"
`; `;
exports[`Reactivity: useState two independent components on different levels are updated in parallel 1`] = ` exports[`Reactivity: useState two components can subscribe to same context 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].value; let txt1 = ctx['contextObj'].value;
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
exports[`Reactivity: useState two independent components on different levels are updated in parallel 2`] = ` exports[`Reactivity: useState two independent components on different levels are updated in parallel 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
`;
exports[`Reactivity: useState two independent components on different levels are updated in parallel 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
@@ -330,17 +288,44 @@ exports[`Reactivity: useState two independent components on different levels are
}" }"
`; `;
exports[`Reactivity: useState two independent components on different levels are updated in parallel 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['contextObj'].value;
return block1([txt1]);
}
}"
`;
exports[`Reactivity: useState two independent components on different levels are updated in parallel 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
`;
exports[`Reactivity: useState useContext=useState hook is reactive, for one component 1`] = ` exports[`Reactivity: useState useContext=useState hook is reactive, for one component 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].value; let txt1 = ctx['contextObj'].value;
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -348,23 +333,8 @@ exports[`Reactivity: useState useContext=useState hook is reactive, for one comp
exports[`Reactivity: useState useless atoms should be deleted 1`] = ` exports[`Reactivity: useState useless atoms should be deleted 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].quantity;
return block1([d1]);
}
}"
`;
exports[`Reactivity: useState useless atoms should be deleted 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/> Total: <block-text-0/> Count: <block-text-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/> Total: <block-text-0/> Count: <block-text-1/></div>\`);
@@ -378,9 +348,23 @@ exports[`Reactivity: useState useless atoms should be deleted 2`] = `
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let d1 = ctx['total']; let txt1 = ctx['total'];
let d2 = Object.keys(ctx['state']).length; let txt2 = Object.keys(ctx['state']).length;
return block1([d1, d2], [b2]); return block1([txt1, txt2], [b2]);
}
}"
`;
exports[`Reactivity: useState useless atoms should be deleted 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['state'].quantity;
return block1([txt1]);
} }
}" }"
`; `;
@@ -388,14 +372,13 @@ exports[`Reactivity: useState useless atoms should be deleted 2`] = `
exports[`Reactivity: useState very simple use, with initial value 1`] = ` exports[`Reactivity: useState very simple use, with initial value 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['contextObj'].value; let txt1 = ctx['contextObj'].value;
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
+43
View File
@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`app App supports env with getters/setters 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/> <block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['env'].someVal;
let txt2 = Object.keys(ctx['env'].services);
return block1([txt1, txt2]);
}
}"
`;
exports[`app can configure an app with props 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['props'].value;
return block1([txt1]);
}
}"
`;
exports[`app destroy remove the widget from the DOM 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
+62
View File
@@ -0,0 +1,62 @@
import { App, Component, xml } from "../../src";
import { status } from "../../src/component/status";
import { makeTestFixture, snapshotEverything, nextTick, elem } from "../helpers";
let fixture: HTMLElement;
snapshotEverything();
beforeEach(() => {
fixture = makeTestFixture();
});
describe("app", () => {
test("destroy remove the widget from the DOM", async () => {
class SomeComponent extends Component {
static template = xml`<div/>`;
}
const app = new App(SomeComponent);
const comp = await app.mount(fixture);
const el = elem(comp);
expect(document.contains(el)).toBe(true);
app.destroy();
expect(document.contains(el)).toBe(false);
expect(status(comp)).toBe("destroyed");
});
test("App supports env with getters/setters", async () => {
let someVal = "maggot";
const services: any = { serv1: "" };
const env = {
get someVal() {
return someVal;
},
services,
};
class SomeComponent extends Component {
static template = xml`<div><t t-esc="env.someVal" /> <t t-esc="Object.keys(env.services)" /></div>`;
}
const app = new App(SomeComponent, { env });
const comp = await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>maggot serv1</div>");
someVal = "brain";
services.serv2 = "";
comp.render();
await nextTick();
expect(fixture.innerHTML).toBe("<div>brain serv1,serv2</div>");
});
test("can configure an app with props", async () => {
class SomeComponent extends Component {
static template = xml`<div t-esc="props.value"/>`;
}
const app = new App(SomeComponent, { props: { value: 333 } });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>333</div>");
});
});
+32
View File
@@ -227,6 +227,38 @@ describe("misc", () => {
expect(fixture.innerHTML).toBe("<p>() =&gt; 3tostring</p>"); expect(fixture.innerHTML).toBe("<p>() =&gt; 3tostring</p>");
}); });
test("block with 2 subblocks: variation", async () => {
const block = createBlock("<a><b><c><block-child-0/>2</c></b><block-child-1/></a>");
const tree = block([], [text("1"), text("3")]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe("<a><b><c>12</c></b>3</a>");
});
test("block with 2 subblocks: another variation", async () => {
const block = createBlock(
`<a block-attribute-0="hello"><b><c><block-child-0/>2</c></b><block-child-1/></a>`
);
const tree = block(["world"], [text("1"), text("3")]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<a hello="world"><b><c>12</c></b>3</a>`);
});
test("namespace is not propagated to siblings", () => {
const block = createBlock(`<div><svg block-ns="someNameSpace"><g/></svg><div></div></div>`);
const fixture = makeTestFixture();
mount(block(), fixture);
expect(fixture.innerHTML).toBe("<div><svg><g></g></svg><div></div></div>");
expect(fixture.querySelector("svg")!.namespaceURI).toBe("someNameSpace");
expect(fixture.querySelector("g")!.namespaceURI).toBe("someNameSpace");
const allDivs = fixture.querySelectorAll("div");
expect(Array.from(allDivs).map((el) => el.namespaceURI)).toEqual([
"http://www.w3.org/1999/xhtml",
"http://www.w3.org/1999/xhtml",
]);
});
// test.skip("reusing a block skips patching process", async () => { // test.skip("reusing a block skips patching process", async () => {
// const block = createBlock('<div><block-text-0/></div>'); // const block = createBlock('<div><block-text-0/></div>');
// const foo = block(["foo"]); // const foo = block(["foo"]);
+68
View File
@@ -26,6 +26,26 @@ test("simple attribute", async () => {
expect(fixture.innerHTML).toBe(`<div hello="owl"></div>`); expect(fixture.innerHTML).toBe(`<div hello="owl"></div>`);
}); });
test("updating attribute with falsy value", async () => {
const block = createBlock('<div block-attribute-0="hello"></div>');
const tree = block([false]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<div></div>`);
patch(tree, block(["owl"]));
expect(fixture.innerHTML).toBe(`<div hello="owl"></div>`);
patch(tree, block([false]));
expect(fixture.innerHTML).toBe(`<div></div>`);
patch(tree, block(["owl"]));
expect(fixture.innerHTML).toBe(`<div hello="owl"></div>`);
patch(tree, block([undefined]));
expect(fixture.innerHTML).toBe(`<div></div>`);
});
test("dynamic attribute (pair)", async () => { test("dynamic attribute (pair)", async () => {
const block = createBlock('<div block-attributes="0"></div>'); const block = createBlock('<div block-attributes="0"></div>');
const tree = block([["hello", "world"]]); const tree = block([["hello", "world"]]);
@@ -37,6 +57,20 @@ test("dynamic attribute (pair)", async () => {
expect(fixture.innerHTML).toBe(`<div ola="mundo"></div>`); expect(fixture.innerHTML).toBe(`<div ola="mundo"></div>`);
}); });
test("dynamic attribute (pair, with false value)", async () => {
const block = createBlock('<div block-attributes="0"></div>');
const tree = block([["hello", false]]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<div></div>`);
patch(tree, block([["hello", "world"]]));
expect(fixture.innerHTML).toBe(`<div hello="world"></div>`);
patch(tree, block([["hello", false]]));
expect(fixture.innerHTML).toBe(`<div></div>`);
});
test("dynamic attribute (object)", async () => { test("dynamic attribute (object)", async () => {
const block = createBlock('<div block-attributes="0"></div>'); const block = createBlock('<div block-attributes="0"></div>');
const tree = block([{ hello: "world" }]); const tree = block([{ hello: "world" }]);
@@ -48,6 +82,23 @@ test("dynamic attribute (object)", async () => {
expect(fixture.innerHTML).toBe(`<div ola="mundo"></div>`); expect(fixture.innerHTML).toBe(`<div ola="mundo"></div>`);
}); });
test("dynamic attribute (object), with falsy values", async () => {
const block = createBlock('<div block-attributes="0"></div>');
const tree = block([{ hello: "world", blip: false }]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<div hello="world"></div>`);
patch(tree, block([{ ola: "mundo", blip: undefined }]));
expect(fixture.innerHTML).toBe(`<div ola="mundo"></div>`);
patch(tree, block([{ ola: false, blip: 1 }]));
expect(fixture.innerHTML).toBe(`<div blip="1"></div>`);
patch(tree, block([{ ola: undefined, blip: undefined }]));
expect(fixture.innerHTML).toBe(`<div></div>`);
});
test("class attribute", async () => { test("class attribute", async () => {
const block = createBlock('<div block-attribute-0="class"></div>'); const block = createBlock('<div block-attribute-0="class"></div>');
const tree = block(["fire"]); const tree = block(["fire"]);
@@ -65,6 +116,14 @@ test("class attribute", async () => {
expect(fixture.innerHTML).toBe(`<div class="0"></div>`); expect(fixture.innerHTML).toBe(`<div class="0"></div>`);
}); });
test("attribute with undefined value", async () => {
const block = createBlock('<div block-attribute-0="abc"></div>');
const tree = block([undefined]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe(`<div></div>`);
});
test("class attribute with undefined value", async () => { test("class attribute with undefined value", async () => {
const block = createBlock('<div block-attribute-0="class"></div>'); const block = createBlock('<div block-attribute-0="class"></div>');
const tree = block([undefined]); const tree = block([undefined]);
@@ -110,6 +169,15 @@ describe("properties", () => {
expect(input.value).toBe("potato"); expect(input.value).toBe("potato");
}); });
test("input with value attribute, and undefined given", () => {
const block = createBlock(`<input block-attribute-0="value"/>`);
const tree = block([undefined]);
mount(tree, fixture);
const input = fixture.querySelector("input")!;
expect(input.value).toBe("");
});
test("input type=checkbox with checked attribute", () => { test("input type=checkbox with checked attribute", () => {
// render input with initial value // render input with initial value
const block = createBlock(`<input type="checkbox" block-attribute-0="checked"/>`); const block = createBlock(`<input type="checkbox" block-attribute-0="checked"/>`);
+13
View File
@@ -1,4 +1,5 @@
import { createBlock, mount, patch, remove } from "../../src/blockdom"; import { createBlock, mount, patch, remove } from "../../src/blockdom";
import { logStep } from "../helpers";
import { makeTestFixture } from "./helpers"; import { makeTestFixture } from "./helpers";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -55,3 +56,15 @@ test("is in dom when callback is called", async () => {
mount(tree, fixture); mount(tree, fixture);
}); });
test("callback ref in callback ref with same block", async () => {
const block = createBlock('<p block-ref="0"><block-text-1/><block-child-0/></p>');
let refFn = (el: HTMLParagraphElement) => logStep(el.outerHTML);
const child = block([refFn, "child"], []);
const parent = block([refFn, "parent"], [child]);
mount(parent, fixture);
expect(fixture.innerHTML).toBe("<p>parent<p>child</p></p>");
expect(["<p>child</p>", "<p>parent<p>child</p></p>"]).toBeLogged();
});
+26
View File
@@ -0,0 +1,26 @@
import { comment, mount } from "../../src/blockdom";
import { makeTestFixture } from "./helpers";
//------------------------------------------------------------------------------
// Setup and helpers
//------------------------------------------------------------------------------
let fixture: HTMLElement;
beforeEach(() => {
fixture = makeTestFixture();
});
afterEach(() => {
fixture.remove();
});
test("simple comment node", async () => {
const tree = comment("foo");
expect(tree.el).toBe(undefined);
mount(tree, fixture);
expect(fixture.innerHTML).toBe("<!--foo-->");
expect(tree.el).not.toBe(undefined);
tree.remove();
expect(fixture.innerHTML).toBe("");
});
+12
View File
@@ -76,4 +76,16 @@ describe("multi blocks", () => {
mount(text(multi([text("a"), text("b")]) as any), fixture); mount(text(multi([text("a"), text("b")]) as any), fixture);
expect(fixture.innerHTML).toBe("ab"); expect(fixture.innerHTML).toBe("ab");
}); });
test("multi vnode can be used as text", () => {
mount(text(multi([text("a"), undefined]) as any), fixture);
expect(fixture.innerHTML).toBe("a");
});
test("multi inside a block", async () => {
const block = createBlock("<div><block-child-0/></div>");
const tree = block([], [multi([text("foo"), text("bar")])]);
mount(tree, fixture);
expect(fixture.innerHTML).toBe("<div>foobar</div>");
});
}); });
@@ -3,14 +3,13 @@
exports[`attributes changing a class with t-att-class (preexisting class 1`] = ` exports[`attributes changing a class with t-att-class (preexisting class 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"hoy\\" block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div class=\\"hoy\\" block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v']; let attr1 = ctx['v'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -18,14 +17,13 @@ exports[`attributes changing a class with t-att-class (preexisting class 1`] = `
exports[`attributes changing a class with t-att-class 1`] = ` exports[`attributes changing a class with t-att-class 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v']; let attr1 = ctx['v'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -33,14 +31,13 @@ exports[`attributes changing a class with t-att-class 1`] = `
exports[`attributes changing an attribute with t-att- 1`] = ` exports[`attributes changing an attribute with t-att- 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"value\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v']; let attr1 = ctx['v'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -48,14 +45,13 @@ exports[`attributes changing an attribute with t-att- 1`] = `
exports[`attributes class and t-att-class should combine together 1`] = ` exports[`attributes class and t-att-class should combine together 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\" class=\\"hello\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\" class=\\"hello\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -63,14 +59,13 @@ exports[`attributes class and t-att-class should combine together 1`] = `
exports[`attributes class and t-attf-class with ternary operation 1`] = ` exports[`attributes class and t-attf-class with ternary operation 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"hello\\" block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div class=\\"hello\\" block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = (ctx['value']?'world':''); let attr1 = (ctx['value']?'world':'');
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -78,14 +73,13 @@ exports[`attributes class and t-attf-class with ternary operation 1`] = `
exports[`attributes dynamic attribute evaluating to 0 1`] = ` exports[`attributes dynamic attribute evaluating to 0 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -93,14 +87,13 @@ exports[`attributes dynamic attribute evaluating to 0 1`] = `
exports[`attributes dynamic attribute falsy variable 1`] = ` exports[`attributes dynamic attribute falsy variable 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -108,14 +101,13 @@ exports[`attributes dynamic attribute falsy variable 1`] = `
exports[`attributes dynamic attribute with a dash 1`] = ` exports[`attributes dynamic attribute with a dash 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"data-action-id\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"data-action-id\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['id']; let attr1 = ctx['id'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -123,14 +115,13 @@ exports[`attributes dynamic attribute with a dash 1`] = `
exports[`attributes dynamic attributes 1`] = ` exports[`attributes dynamic attributes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = 'bar'; let attr1 = 'bar';
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -138,14 +129,13 @@ exports[`attributes dynamic attributes 1`] = `
exports[`attributes dynamic class attribute 1`] = ` exports[`attributes dynamic class attribute 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['c']; let attr1 = ctx['c'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -153,14 +143,13 @@ exports[`attributes dynamic class attribute 1`] = `
exports[`attributes dynamic class attribute evaluating to 0 1`] = ` exports[`attributes dynamic class attribute evaluating to 0 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -168,14 +157,13 @@ exports[`attributes dynamic class attribute evaluating to 0 1`] = `
exports[`attributes dynamic empty class attribute 1`] = ` exports[`attributes dynamic empty class attribute 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['c']; let attr1 = ctx['c'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -183,14 +171,41 @@ exports[`attributes dynamic empty class attribute 1`] = `
exports[`attributes dynamic formatted attributes with a dash 1`] = ` exports[`attributes dynamic formatted attributes with a dash 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"aria-label\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"aria-label\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`Some text \${ctx['id']}\`; let attr1 = \`Some text \${ctx['id']}\`;
return block1([d1]); return block1([attr1]);
}
}"
`;
exports[`attributes dynamic undefined class attribute 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = ctx['c'];
return block1([attr1]);
}
}"
`;
exports[`attributes dynamic undefined generic attribute 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div block-attribute-0=\\"thing\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = ctx['c'];
return block1([attr1]);
} }
}" }"
`; `;
@@ -198,14 +213,13 @@ exports[`attributes dynamic formatted attributes with a dash 1`] = `
exports[`attributes fixed variable 1`] = ` exports[`attributes fixed variable 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -213,14 +227,13 @@ exports[`attributes fixed variable 1`] = `
exports[`attributes format expression 1`] = ` exports[`attributes format expression 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = (ctx['value']+37); let attr1 = (ctx['value']+37);
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -228,14 +241,13 @@ exports[`attributes format expression 1`] = `
exports[`attributes format literal 1`] = ` exports[`attributes format literal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`bar\`; let attr1 = \`bar\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -243,14 +255,13 @@ exports[`attributes format literal 1`] = `
exports[`attributes format multiple 1`] = ` exports[`attributes format multiple 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`a \${ctx['value1']} is \${ctx['value2']} of \${ctx['value3']} ]\`; let attr1 = \`a \${ctx['value1']} is \${ctx['value2']} of \${ctx['value3']} ]\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -258,14 +269,13 @@ exports[`attributes format multiple 1`] = `
exports[`attributes format value 1`] = ` exports[`attributes format value 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`b\${ctx['value']}r\`; let attr1 = \`b\${ctx['value']}r\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -273,8 +283,8 @@ exports[`attributes format value 1`] = `
exports[`attributes from object variables set previously 1`] = ` exports[`attributes from object variables set previously 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><span block-attribute-0=\\"class\\"/></div>\`); let block1 = createBlock(\`<div><span block-attribute-0=\\"class\\"/></div>\`);
@@ -282,8 +292,8 @@ exports[`attributes from object variables set previously 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"o\\", {a:'b'}); setContextValue(ctx, \\"o\\", {a:'b'});
let d1 = ctx['o'].a; let attr1 = ctx['o'].a;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -291,8 +301,8 @@ exports[`attributes from object variables set previously 1`] = `
exports[`attributes from variables set previously (no external node) 1`] = ` exports[`attributes from variables set previously (no external node) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<span block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<span block-attribute-0=\\"class\\"/>\`);
@@ -300,8 +310,8 @@ exports[`attributes from variables set previously (no external node) 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"abc\\", 'def'); setContextValue(ctx, \\"abc\\", 'def');
let d1 = ctx['abc']; let attr1 = ctx['abc'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -309,8 +319,8 @@ exports[`attributes from variables set previously (no external node) 1`] = `
exports[`attributes from variables set previously 1`] = ` exports[`attributes from variables set previously 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><span block-attribute-0=\\"class\\"/></div>\`); let block1 = createBlock(\`<div><span block-attribute-0=\\"class\\"/></div>\`);
@@ -318,8 +328,8 @@ exports[`attributes from variables set previously 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"abc\\", 'def'); setContextValue(ctx, \\"abc\\", 'def');
let d1 = ctx['abc']; let attr1 = ctx['abc'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -327,14 +337,13 @@ exports[`attributes from variables set previously 1`] = `
exports[`attributes object 1`] = ` exports[`attributes object 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attributes=\\"0\\"/>\`); let block1 = createBlock(\`<div block-attributes=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -342,8 +351,7 @@ exports[`attributes object 1`] = `
exports[`attributes static attributes 1`] = ` exports[`attributes static attributes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div foo=\\"a\\" bar=\\"b\\" baz=\\"c\\"/>\`); let block1 = createBlock(\`<div foo=\\"a\\" bar=\\"b\\" baz=\\"c\\"/>\`);
@@ -356,8 +364,7 @@ exports[`attributes static attributes 1`] = `
exports[`attributes static attributes on void elements 1`] = ` exports[`attributes static attributes on void elements 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<img src=\\"/test.skip.jpg\\" alt=\\"Test\\"/>\`); let block1 = createBlock(\`<img src=\\"/test.skip.jpg\\" alt=\\"Test\\"/>\`);
@@ -370,8 +377,7 @@ exports[`attributes static attributes on void elements 1`] = `
exports[`attributes static attributes with dashes 1`] = ` exports[`attributes static attributes with dashes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div aria-label=\\"Close\\"/>\`); let block1 = createBlock(\`<div aria-label=\\"Close\\"/>\`);
@@ -384,14 +390,13 @@ exports[`attributes static attributes with dashes 1`] = `
exports[`attributes t-att-class and class should combine together 1`] = ` exports[`attributes t-att-class and class should combine together 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"hello\\" block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div class=\\"hello\\" block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -399,14 +404,13 @@ exports[`attributes t-att-class and class should combine together 1`] = `
exports[`attributes t-att-class with multiple classes 1`] = ` exports[`attributes t-att-class with multiple classes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = {'a b c':ctx['value']}; let attr1 = {'a b c':ctx['value']};
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -414,14 +418,13 @@ exports[`attributes t-att-class with multiple classes 1`] = `
exports[`attributes t-att-class with multiple classes 2`] = ` exports[`attributes t-att-class with multiple classes 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = {['a b c']:ctx['value']}; let attr1 = {['a b c']:ctx['value']};
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -429,14 +432,13 @@ exports[`attributes t-att-class with multiple classes 2`] = `
exports[`attributes t-att-class with multiple classes, some of which are duplicate 1`] = ` exports[`attributes t-att-class with multiple classes, some of which are duplicate 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = {'a b c':ctx['value'],'a b d':!ctx['value']}; let attr1 = {'a b c':ctx['value'],'a b d':!ctx['value']};
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -444,14 +446,13 @@ exports[`attributes t-att-class with multiple classes, some of which are duplica
exports[`attributes t-att-class with object 1`] = ` exports[`attributes t-att-class with object 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"static\\" block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div class=\\"static\\" block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = {a:ctx['b'],c:ctx['d'],e:ctx['f']}; let attr1 = {a:ctx['b'],c:ctx['d'],e:ctx['f']};
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -459,14 +460,13 @@ exports[`attributes t-att-class with object 1`] = `
exports[`attributes t-attf-class 1`] = ` exports[`attributes t-attf-class 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`hello\`; let attr1 = \`hello\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -474,14 +474,13 @@ exports[`attributes t-attf-class 1`] = `
exports[`attributes t-attf-class should combine with class 1`] = ` exports[`attributes t-attf-class should combine with class 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"hello\\" block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div class=\\"hello\\" block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`world\`; let attr1 = \`world\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -489,14 +488,13 @@ exports[`attributes t-attf-class should combine with class 1`] = `
exports[`attributes t-attf-class with multiple classes 1`] = ` exports[`attributes t-attf-class with multiple classes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`hello \${ctx['word']}\`; let attr1 = \`hello \${ctx['word']}\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -504,14 +502,13 @@ exports[`attributes t-attf-class with multiple classes 1`] = `
exports[`attributes t-attf-class with multiple classes separated by multiple spaces 1`] = ` exports[`attributes t-attf-class with multiple classes separated by multiple spaces 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = \`hello \${ctx['word']}\`; let attr1 = \`hello \${ctx['word']}\`;
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -519,14 +516,13 @@ exports[`attributes t-attf-class with multiple classes separated by multiple spa
exports[`attributes tuple literal 1`] = ` exports[`attributes tuple literal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attributes=\\"0\\"/>\`); let block1 = createBlock(\`<div block-attributes=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ['foo','bar']; let attr1 = ['foo','bar'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -534,14 +530,13 @@ exports[`attributes tuple literal 1`] = `
exports[`attributes tuple variable 1`] = ` exports[`attributes tuple variable 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attributes=\\"0\\"/>\`); let block1 = createBlock(\`<div block-attributes=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -549,8 +544,7 @@ exports[`attributes tuple variable 1`] = `
exports[`attributes two classes 1`] = ` exports[`attributes two classes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"a b\\"/>\`); let block1 = createBlock(\`<div class=\\"a b\\"/>\`);
@@ -563,15 +557,14 @@ exports[`attributes two classes 1`] = `
exports[`attributes two dynamic attributes 1`] = ` exports[`attributes two dynamic attributes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\" block-attribute-1=\\"bar\\"/>\`); let block1 = createBlock(\`<div block-attribute-0=\\"foo\\" block-attribute-1=\\"bar\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = 'bar'; let attr1 = 'bar';
let d2 = 'foo'; let attr2 = 'foo';
return block1([d1, d2]); return block1([attr1, attr2]);
} }
}" }"
`; `;
@@ -579,14 +572,13 @@ exports[`attributes two dynamic attributes 1`] = `
exports[`attributes updating classes (with obj notation) 1`] = ` exports[`attributes updating classes (with obj notation) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"hoy\\" block-attribute-0=\\"class\\"/>\`); let block1 = createBlock(\`<div class=\\"hoy\\" block-attribute-0=\\"class\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = {'a b':ctx['condition']}; let attr1 = {'a b':ctx['condition']};
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -594,16 +586,15 @@ exports[`attributes updating classes (with obj notation) 1`] = `
exports[`attributes various escapes 1`] = ` exports[`attributes various escapes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div foo=\\"&lt;foo\\" block-attribute-0=\\"bar\\" block-attribute-1=\\"baz\\" block-attributes=\\"2\\"/>\`); let block1 = createBlock(\`<div foo=\\"&lt;foo\\" block-attribute-0=\\"bar\\" block-attribute-1=\\"baz\\" block-attributes=\\"2\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['bar']; let attr1 = ctx['bar'];
let d2 = \`<\${ctx['baz']}>\`; let attr2 = \`<\${ctx['baz']}>\`;
let d3 = ctx['qux']; let attr3 = ctx['qux'];
return block1([d1, d2, d3]); return block1([attr1, attr2, attr3]);
} }
}" }"
`; `;
@@ -611,8 +602,7 @@ exports[`attributes various escapes 1`] = `
exports[`attributes various escapes 2 1`] = ` exports[`attributes various escapes 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div> &lt; </div>\`); let block1 = createBlock(\`<div> &lt; </div>\`);
@@ -625,14 +615,13 @@ exports[`attributes various escapes 2 1`] = `
exports[`special cases for some specific html attributes/properties input of type checkbox with t-att-indeterminate 1`] = ` exports[`special cases for some specific html attributes/properties input of type checkbox with t-att-indeterminate 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<input type=\\"checkbox\\" block-attribute-0=\\"indeterminate\\"/>\`); let block1 = createBlock(\`<input type=\\"checkbox\\" block-attribute-0=\\"indeterminate\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v']; let attr1 = ctx['v'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -640,14 +629,13 @@ exports[`special cases for some specific html attributes/properties input of typ
exports[`special cases for some specific html attributes/properties input type= checkbox, with t-att-checked 1`] = ` exports[`special cases for some specific html attributes/properties input type= checkbox, with t-att-checked 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<input type=\\"checkbox\\" block-attribute-0=\\"checked\\"/>\`); let block1 = createBlock(\`<input type=\\"checkbox\\" block-attribute-0=\\"checked\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['flag']; let attr1 = ctx['flag'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -655,14 +643,13 @@ exports[`special cases for some specific html attributes/properties input type=
exports[`special cases for some specific html attributes/properties input with t-att-value 1`] = ` exports[`special cases for some specific html attributes/properties input with t-att-value 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`); let block1 = createBlock(\`<input block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v']; let attr1 = ctx['v'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -670,14 +657,13 @@ exports[`special cases for some specific html attributes/properties input with t
exports[`special cases for some specific html attributes/properties select with t-att-value 1`] = ` exports[`special cases for some specific html attributes/properties select with t-att-value 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<select block-attribute-0=\\"value\\"><option value=\\"potato\\">Potato</option><option value=\\"tomato\\">Tomato</option><option value=\\"onion\\">Onion</option></select>\`); let block1 = createBlock(\`<select block-attribute-0=\\"value\\"><option value=\\"potato\\">Potato</option><option value=\\"tomato\\">Tomato</option><option value=\\"onion\\">Onion</option></select>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['value']; let attr1 = ctx['value'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -685,14 +671,13 @@ exports[`special cases for some specific html attributes/properties select with
exports[`special cases for some specific html attributes/properties textarea with t-att-value 1`] = ` exports[`special cases for some specific html attributes/properties textarea with t-att-value 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<textarea block-attribute-0=\\"value\\"/>\`); let block1 = createBlock(\`<textarea block-attribute-0=\\"value\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v']; let attr1 = ctx['v'];
return block1([d1]); return block1([attr1]);
} }
}" }"
`; `;
@@ -700,8 +685,7 @@ exports[`special cases for some specific html attributes/properties textarea wit
exports[`special cases for some specific html attributes/properties various boolean html attributes 1`] = ` exports[`special cases for some specific html attributes/properties various boolean html attributes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><input type=\\"checkbox\\" checked=\\"checked\\"/><input checked=\\"checked\\"/><div checked=\\"checked\\"/><div selected=\\"selected\\"/><option selected=\\"selected\\" other=\\"1\\"/><input readonly=\\"readonly\\"/><button disabled=\\"disabled\\"/></div>\`); let block1 = createBlock(\`<div><input type=\\"checkbox\\" checked=\\"checked\\"/><input checked=\\"checked\\"/><div checked=\\"checked\\"/><div selected=\\"selected\\"/><option selected=\\"selected\\" other=\\"1\\"/><input readonly=\\"readonly\\"/><button disabled=\\"disabled\\"/></div>\`);
@@ -3,13 +3,10 @@
exports[`comments only a comment 1`] = ` exports[`comments only a comment 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<!-- comment-->\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return block1(); return comment(\` comment\`);
} }
}" }"
`; `;
@@ -17,8 +14,7 @@ exports[`comments only a comment 1`] = `
exports[`comments properly handle comments 1`] = ` exports[`comments properly handle comments 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>hello <!-- comment-->owl</div>\`); let block1 = createBlock(\`<div>hello <!-- comment-->owl</div>\`);
@@ -31,8 +27,7 @@ exports[`comments properly handle comments 1`] = `
exports[`comments properly handle comments between t-if/t-else 1`] = ` exports[`comments properly handle comments between t-if/t-else 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
let block2 = createBlock(\`<span>true</span>\`); let block2 = createBlock(\`<span>true</span>\`);
@@ -1,15 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`error handling cannot add twice the same template 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
@@ -3,14 +3,13 @@
exports[`t-on can bind event handler 1`] = ` exports[`t-on can bind event handler 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx]; let hdlr1 = [ctx['add'], ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -18,15 +17,14 @@ exports[`t-on can bind event handler 1`] = `
exports[`t-on can bind handlers with arguments 1`] = ` exports[`t-on can bind handlers with arguments 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; const v1 = ctx['add'];
let d1 = [()=>v1(5), ctx]; let hdlr1 = [()=>v1(5), ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -34,15 +32,14 @@ exports[`t-on can bind handlers with arguments 1`] = `
exports[`t-on can bind handlers with empty object 1`] = ` exports[`t-on can bind handlers with empty object 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['doSomething']; const v1 = ctx['doSomething'];
let d1 = [()=>v1({}), ctx]; let hdlr1 = [()=>v1({}), ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -50,15 +47,14 @@ exports[`t-on can bind handlers with empty object 1`] = `
exports[`t-on can bind handlers with empty object (with non empty inner string) 1`] = ` exports[`t-on can bind handlers with empty object (with non empty inner string) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['doSomething']; const v1 = ctx['doSomething'];
let d1 = [()=>v1({}), ctx]; let hdlr1 = [()=>v1({}), ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -66,8 +62,8 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
exports[`t-on can bind handlers with empty object (with non empty inner string) 2`] = ` exports[`t-on can bind handlers with empty object (with non empty inner string) 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<ul><block-child-0/></ul>\`); let block1 = createBlock(\`<ul><block-child-0/></ul>\`);
let block3 = createBlock(\`<li><a block-handler-0=\\"click\\">link</a></li>\`); let block3 = createBlock(\`<li><a block-handler-0=\\"click\\">link</a></li>\`);
@@ -81,8 +77,8 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
let key1 = ctx['action_index']; let key1 = ctx['action_index'];
const v1 = ctx['activate']; const v1 = ctx['activate'];
const v2 = ctx['action']; const v2 = ctx['action'];
let d1 = [()=>v1(v2), ctx]; let hdlr1 = [()=>v1(v2), ctx];
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([hdlr1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
@@ -93,15 +89,14 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
exports[`t-on can bind handlers with object arguments 1`] = ` exports[`t-on can bind handlers with object arguments 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add']; const v1 = ctx['add'];
let d1 = [()=>v1({val:5}), ctx]; let hdlr1 = [()=>v1({val:5}), ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -109,15 +104,14 @@ exports[`t-on can bind handlers with object arguments 1`] = `
exports[`t-on can bind two event handlers 1`] = ` exports[`t-on can bind two event handlers 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['handleClick'], ctx]; let hdlr1 = [ctx['handleClick'], ctx];
let d2 = [ctx['handleDblClick'], ctx]; let hdlr2 = [ctx['handleDblClick'], ctx];
return block1([d1, d2]); return block1([hdlr1, hdlr2]);
} }
}" }"
`; `;
@@ -125,14 +119,13 @@ exports[`t-on can bind two event handlers 1`] = `
exports[`t-on handler is bound to proper owner 1`] = ` exports[`t-on handler is bound to proper owner 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx]; let hdlr1 = [ctx['add'], ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -140,8 +133,8 @@ exports[`t-on handler is bound to proper owner 1`] = `
exports[`t-on handler is bound to proper owner, part 2 1`] = ` exports[`t-on handler is bound to proper owner, part 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block2 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block2 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
@@ -151,8 +144,8 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) { for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`value\`] = v_block1[i1]; ctx[\`value\`] = v_block1[i1];
let key1 = ctx['value']; let key1 = ctx['value'];
let d1 = [ctx['add'], ctx]; let hdlr1 = [ctx['add'], ctx];
c_block1[i1] = withKey(block2([d1]), key1); c_block1[i1] = withKey(block2([hdlr1]), key1);
} }
return list(c_block1); return list(c_block1);
} }
@@ -162,14 +155,12 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
exports[`t-on handler is bound to proper owner, part 3 1`] = ` exports[`t-on handler is bound to proper owner, part 3 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx]; return callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([d1]);
} }
}" }"
`; `;
@@ -177,12 +168,13 @@ exports[`t-on handler is bound to proper owner, part 3 1`] = `
exports[`t-on handler is bound to proper owner, part 3 2`] = ` exports[`t-on handler is bound to proper owner, part 3 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return callTemplate_2.call(this, ctx, node, key + \`__1\`); let hdlr1 = [ctx['add'], ctx];
return block1([hdlr1]);
} }
}" }"
`; `;
@@ -190,24 +182,9 @@ exports[`t-on handler is bound to proper owner, part 3 2`] = `
exports[`t-on handler is bound to proper owner, part 4 1`] = ` exports[`t-on handler is bound to proper owner, part 4 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, getTemplate, withKey } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx];
return block1([d1]);
}
}"
`;
exports[`t-on handler is bound to proper owner, part 4 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -219,24 +196,37 @@ exports[`t-on handler is bound to proper owner, part 4 2`] = `
ctx[\`value_index\`] = i1; ctx[\`value_index\`] = i1;
ctx[\`value_value\`] = k_block1[i1]; ctx[\`value_value\`] = k_block1[i1];
let key1 = ctx['value']; let key1 = ctx['value'];
c_block1[i1] = withKey(callTemplate_2.call(this, ctx, node, key + \`__1__\${key1}\`), key1); c_block1[i1] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`), key1);
} }
return list(c_block1); return list(c_block1);
} }
}" }"
`; `;
exports[`t-on handler is bound to proper owner, part 4 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['add'], ctx];
return block1([hdlr1]);
}
}"
`;
exports[`t-on receive event in first argument 1`] = ` exports[`t-on receive event in first argument 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['add'], ctx]; let hdlr1 = [ctx['add'], ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -244,15 +234,14 @@ exports[`t-on receive event in first argument 1`] = `
exports[`t-on t-on modifiers (native listener) basic support for native listener 1`] = ` exports[`t-on t-on modifiers (native listener) basic support for native listener 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"myClass\\" block-handler-0=\\"click\\"><button block-handler-1=\\"click\\">Button</button></div>\`); let block1 = createBlock(\`<div class=\\"myClass\\" block-handler-0=\\"click\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['divClicked'], ctx]; let hdlr1 = [ctx['divClicked'], ctx];
let d2 = [ctx['btnClicked'], ctx]; let hdlr2 = [ctx['btnClicked'], ctx];
return block1([d1, d2]); return block1([hdlr1, hdlr2]);
} }
}" }"
`; `;
@@ -260,15 +249,14 @@ exports[`t-on t-on modifiers (native listener) basic support for native listener
exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = ` exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-text-1/></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-text-1/></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx]; let hdlr1 = [ctx['onClick'], ctx];
let d2 = ctx['text']; let txt1 = ctx['text'];
return block1([d1, d2]); return block1([hdlr1, txt1]);
} }
}" }"
`; `;
@@ -276,15 +264,15 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = `
exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = ` exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-child-0/></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-child-0/></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx]; let hdlr1 = [ctx['onClick'], ctx];
let b2 = safeOutput(ctx['html']); let b2 = safeOutput(ctx['html']);
return block1([d1], [b2]); return block1([hdlr1], [b2]);
} }
}" }"
`; `;
@@ -292,15 +280,14 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = `
exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-handler-0=\\"click.capture\\"><button block-handler-1=\\"click\\">Button</button></div>\`); let block1 = createBlock(\`<div block-handler-0=\\"click.capture\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [\\"capture\\", ctx['onCapture'], ctx]; let hdlr1 = [\\"capture\\", ctx['onCapture'], ctx];
let d2 = [ctx['doSomething'], ctx]; let hdlr2 = [ctx['doSomething'], ctx];
return block1([d1, d2]); return block1([hdlr1, hdlr2]);
} }
}" }"
`; `;
@@ -308,14 +295,13 @@ exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] =
exports[`t-on t-on modifiers (native listener) t-on with empty handler (only modifiers) 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with empty handler (only modifiers) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button</button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [\\"prevent\\", , ctx]; let hdlr1 = [\\"prevent\\", , ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -323,14 +309,13 @@ exports[`t-on t-on modifiers (native listener) t-on with empty handler (only mod
exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifiers (order matters) 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifiers (order matters) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent.self\\"><span>Button</span></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent.self\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx]; let hdlr1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -338,16 +323,15 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifi
exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop modifiers 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop modifiers 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button 1</button><button block-handler-1=\\"click.stop\\">Button 2</button><button block-handler-2=\\"click.prevent.stop\\">Button 3</button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button 1</button><button block-handler-1=\\"click.stop\\">Button 2</button><button block-handler-2=\\"click.prevent.stop\\">Button 3</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [\\"prevent\\", ctx['onClickPrevented'], ctx]; let hdlr1 = [\\"prevent\\", ctx['onClickPrevented'], ctx];
let d2 = [\\"stop\\", ctx['onClickStopped'], ctx]; let hdlr2 = [\\"stop\\", ctx['onClickStopped'], ctx];
let d3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx]; let hdlr3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx];
return block1([d1, d2, d3]); return block1([hdlr1, hdlr2, hdlr3]);
} }
}" }"
`; `;
@@ -355,8 +339,8 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop mod
exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-foreach 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<a href=\\"#\\" block-handler-0=\\"click.prevent\\"> Edit <block-text-1/></a>\`); let block3 = createBlock(\`<a href=\\"#\\" block-handler-0=\\"click.prevent\\"> Edit <block-text-1/></a>\`);
@@ -369,9 +353,9 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
let key1 = ctx['project']; let key1 = ctx['project'];
const v1 = ctx['onEdit']; const v1 = ctx['onEdit'];
const v2 = ctx['project']; const v2 = ctx['project'];
let d1 = [\\"prevent\\", ev=>v1(v2.id,ev), ctx]; let hdlr1 = [\\"prevent\\", _ev=>v1(v2.id,_ev), ctx];
let d2 = ctx['project'].name; let txt1 = ctx['project'].name;
c_block2[i1] = withKey(block3([d1, d2]), key1); c_block2[i1] = withKey(block3([hdlr1, txt1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
@@ -382,14 +366,13 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent modifier in t-f
exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifiers (order matters) 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifiers (order matters) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click.self.prevent\\"><span>Button</span></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click.self.prevent\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx]; let hdlr1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -397,15 +380,14 @@ exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifi
exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = ` exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><span>Button</span></button><button block-handler-1=\\"click.self\\"><span>Button</span></button></div>\`); let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><span>Button</span></button><button block-handler-1=\\"click.self\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['onClick'], ctx]; let hdlr1 = [ctx['onClick'], ctx];
let d2 = [\\"self\\", ctx['onClickSelf'], ctx]; let hdlr2 = [\\"self\\", ctx['onClickSelf'], ctx];
return block1([d1, d2]); return block1([hdlr1, hdlr2]);
} }
}" }"
`; `;
@@ -413,15 +395,14 @@ exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = `
exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`] = ` exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div block-handler-0=\\"click.synthetic\\"><button block-handler-1=\\"click.synthetic\\">Button</button></div>\`); let block1 = createBlock(\`<div block-handler-0=\\"click.synthetic\\"><button block-handler-1=\\"click.synthetic\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [\\"synthetic\\", ctx['divClicked'], ctx]; let hdlr1 = [\\"synthetic\\", ctx['divClicked'], ctx];
let d2 = [\\"synthetic\\", ctx['btnClicked'], ctx]; let hdlr2 = [\\"synthetic\\", ctx['btnClicked'], ctx];
return block1([d1, d2]); return block1([hdlr1, hdlr2]);
} }
}" }"
`; `;
@@ -429,15 +410,14 @@ exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`
exports[`t-on t-on with inline statement (function call) 1`] = ` exports[`t-on t-on with inline statement (function call) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['state']; const v1 = ctx['state'];
let d1 = [()=>v1.incrementCounter(2), ctx]; let hdlr1 = [()=>v1.incrementCounter(2), ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -445,15 +425,14 @@ exports[`t-on t-on with inline statement (function call) 1`] = `
exports[`t-on t-on with inline statement 1`] = ` exports[`t-on t-on with inline statement 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['state']; const v1 = ctx['state'];
let d1 = [()=>v1.counter++, ctx]; let hdlr1 = [()=>v1.counter++, ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -461,15 +440,14 @@ exports[`t-on t-on with inline statement 1`] = `
exports[`t-on t-on with inline statement, part 2 1`] = ` exports[`t-on t-on with inline statement, part 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Toggle</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Toggle</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['state']; const v1 = ctx['state'];
let d1 = [()=>v1.flag=!v1.flag, ctx]; let hdlr1 = [()=>v1.flag=!v1.flag, ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -477,16 +455,15 @@ exports[`t-on t-on with inline statement, part 2 1`] = `
exports[`t-on t-on with inline statement, part 3 1`] = ` exports[`t-on t-on with inline statement, part 3 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Toggle</button>\`); let block1 = createBlock(\`<button block-handler-0=\\"click\\">Toggle</button>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['state']; const v1 = ctx['state'];
const v2 = ctx['someFunction']; const v2 = ctx['someFunction'];
let d1 = [()=>v1.n=v2(3), ctx]; let hdlr1 = [()=>v1.n=v2(3), ctx];
return block1([d1]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -494,14 +471,15 @@ exports[`t-on t-on with inline statement, part 3 1`] = `
exports[`t-on t-on with t-call 1`] = ` exports[`t-on t-on with t-call 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['update'], ctx]; let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([d1]); return block1([], [b2]);
} }
}" }"
`; `;
@@ -509,15 +487,13 @@ exports[`t-on t-on with t-call 1`] = `
exports[`t-on t-on with t-call 2`] = ` exports[`t-on t-on with t-call 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = callTemplate_2.call(this, ctx, node, key + \`__1\`); let hdlr1 = [ctx['update'], ctx];
return block1([], [b2]); return block1([hdlr1]);
} }
}" }"
`; `;
@@ -525,15 +501,15 @@ exports[`t-on t-on with t-call 2`] = `
exports[`t-on t-on, with arguments and t-call 1`] = ` exports[`t-on t-on, with arguments and t-call 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const v1 = ctx['value']; let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
let d1 = [()=>this.update(v1), ctx]; return block1([], [b2]);
return block1([d1]);
} }
}" }"
`; `;
@@ -541,15 +517,14 @@ exports[`t-on t-on, with arguments and t-call 1`] = `
exports[`t-on t-on, with arguments and t-call 2`] = ` exports[`t-on t-on, with arguments and t-call 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = callTemplate_2.call(this, ctx, node, key + \`__1\`); const v1 = ctx['value'];
return block1([], [b2]); let hdlr1 = [()=>this.update(v1), ctx];
return block1([hdlr1]);
} }
}" }"
`; `;
+124 -124
View File
@@ -3,8 +3,8 @@
exports[`misc complex template 1`] = ` exports[`misc complex template 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div block-attribute-0=\\"class\\"><div block-attribute-1=\\"class\\"><div class=\\"batch_header\\"><a block-attribute-2=\\"href\\" block-attribute-3=\\"class\\" title=\\"View Batch\\"><block-text-4/><block-child-0/><i class=\\"arrow fa fa-window-maximize\\"/></a></div><block-child-1/><div class=\\"batch_slots\\"><block-child-2/><block-child-3/></div><div class=\\"batch_commits\\"><block-child-4/></div></div></div>\`); let block1 = createBlock(\`<div block-attribute-0=\\"class\\"><div block-attribute-1=\\"class\\"><div class=\\"batch_header\\"><a block-attribute-2=\\"href\\" block-attribute-3=\\"class\\" title=\\"View Batch\\"><block-text-4/><block-child-0/><i class=\\"arrow fa fa-window-maximize\\"/></a></div><block-child-1/><div class=\\"batch_slots\\"><block-child-2/><block-child-3/></div><div class=\\"batch_commits\\"><block-child-4/></div></div></div>\`);
let block2 = createBlock(\`<i class=\\"fa fa-exclamation-triangle\\"/>\`); let block2 = createBlock(\`<i class=\\"fa fa-exclamation-triangle\\"/>\`);
@@ -18,11 +18,11 @@ exports[`misc complex template 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3,b4,b6,b8; let b2,b3,b4,b6,b8;
let d1 = \`batch_tile \${ctx['options'].more?'more':'nomore'}\`; let attr1 = \`batch_tile \${ctx['options'].more?'more':'nomore'}\`;
let d2 = \`card bg-\${ctx['klass']}-light\`; let attr2 = \`card bg-\${ctx['klass']}-light\`;
let d3 = \`/runbot/batch/\${ctx['batch'].id}\`; let attr3 = \`/runbot/batch/\${ctx['batch'].id}\`;
let d4 = \`badge badge-\${ctx['batch'].has_warning?'warning':'light'}\`; let attr4 = \`badge badge-\${ctx['batch'].has_warning?'warning':'light'}\`;
let d5 = ctx['batch'].formated_age; let txt1 = ctx['batch'].formated_age;
if (ctx['batch'].has_warning) { if (ctx['batch'].has_warning) {
b2 = block2(); b2 = block2();
} }
@@ -30,11 +30,11 @@ exports[`misc complex template 1`] = `
b3 = block3(); b3 = block3();
} }
ctx = Object.create(ctx); ctx = Object.create(ctx);
const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(slot=>slot.build_id.id&&!slot.trigger_id.manual&&(ctx['options'].trigger_display[slot.trigger_id.id]))); const [k_block4, v_block4, l_block4, c_block4] = prepareList(ctx['batch'].slot_ids.filter(_slot=>_slot.build_id.id&&!_slot.trigger_id.manual&&(ctx['options'].trigger_display[_slot.trigger_id.id])));
for (let i1 = 0; i1 < l_block4; i1++) { for (let i1 = 0; i1 < l_block4; i1++) {
ctx[\`slot\`] = v_block4[i1]; ctx[\`slot\`] = v_block4[i1];
let key1 = ctx['slot'].id; let key1 = ctx['slot'].id;
c_block4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'],slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1); c_block4[i1] = withKey(component(\`SlotButton\`, {class: ctx['slot_container'], slot: ctx['slot']}, key + \`__1__\${key1}\`, node, ctx), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
b4 = list(c_block4); b4 = list(c_block4);
@@ -53,8 +53,8 @@ exports[`misc complex template 1`] = `
ctx[\`commit_link\`] = v_block8[i1]; ctx[\`commit_link\`] = v_block8[i1];
let key1 = ctx['commit_link'].id; let key1 = ctx['commit_link'].id;
let b10,b11,b12,b13; let b10,b11,b12,b13;
let d6 = \`/runbot/commit/\${ctx['commit_link'].commit_id}\`; let attr5 = \`/runbot/commit/\${ctx['commit_link'].commit_id}\`;
let d7 = \`badge badge-light batch_commit match_type_\${ctx['commit_link'].match_type}\`; let attr6 = \`badge badge-light batch_commit match_type_\${ctx['commit_link'].match_type}\`;
if (ctx['commit_link'].match_type=='new') { if (ctx['commit_link'].match_type=='new') {
b10 = block10(); b10 = block10();
} }
@@ -67,13 +67,13 @@ exports[`misc complex template 1`] = `
if (ctx['commit_link'].match_type=='base_head') { if (ctx['commit_link'].match_type=='base_head') {
b13 = block13(); b13 = block13();
} }
let d8 = ctx['commit_link'].commit_dname; let txt2 = ctx['commit_link'].commit_dname;
let d9 = 'https://%s/commit/%s'%(ctx['commit_link'].commit_remote_url,ctx['commit_link'].commit_name); let attr7 = 'https://%s/commit/%s'%(ctx['commit_link'].commit_remote_url,ctx['commit_link'].commit_name);
let d10 = ctx['commit_link'].commit_subject; let txt3 = ctx['commit_link'].commit_subject;
c_block8[i1] = withKey(block9([d6, d7, d8, d9, d10], [b10, b11, b12, b13]), key1); c_block8[i1] = withKey(block9([attr5, attr6, txt2, attr7, txt3], [b10, b11, b12, b13]), key1);
} }
b8 = list(c_block8); b8 = list(c_block8);
return block1([d1, d2, d3, d4, d5], [b2, b3, b4, b6, b8]); return block1([attr1, attr2, attr3, attr4, txt1], [b2, b3, b4, b6, b8]);
} }
}" }"
`; `;
@@ -81,60 +81,13 @@ exports[`misc complex template 1`] = `
exports[`misc global 1`] = ` exports[`misc global 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, isBoundary, withDefault, setContextValue, getTemplate, zero, withKey } = helpers;
const callTemplate_1 = getTemplate(\`_callee-uses-foo\`);
let block1 = createBlock(\`<año block-attribute-0=\\"falló\\"><block-child-0/></año>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = 'agüero';
let b2 = ctx[zero];
return block1([d1], [b2]);
}
}"
`;
exports[`misc global 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = withDefault(ctx['foo'], \`foo default\`);
return block1([d1]);
}
}"
`;
exports[`misc global 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b3 = text(\`toto default\`);
let b2 = withDefault(safeOutput(ctx['toto']), b3);
return block1([], [b2]);
}
}"
`;
exports[`misc global 4`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`_callee-uses-foo\`); const callTemplate_2 = getTemplate(\`_callee-uses-foo\`);
const callTemplate_4 = getTemplate(\`_callee-uses-foo\`); const callTemplate_3 = getTemplate(\`_callee-uses-foo\`);
const callTemplate_6 = getTemplate(\`_callee-uses-foo\`); const callTemplate_4 = getTemplate(\`_callee-asc\`);
const callTemplate_8 = getTemplate(\`_callee-asc\`); const callTemplate_5 = getTemplate(\`_callee-asc-toto\`);
const callTemplate_10 = getTemplate(\`_callee-asc-toto\`);
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
let block4 = createBlock(\`<span><block-text-0/></span>\`); let block4 = createBlock(\`<span><block-text-0/></span>\`);
@@ -151,38 +104,85 @@ exports[`misc global 4`] = `
ctx[\`value_index\`] = i1; ctx[\`value_index\`] = i1;
ctx[\`value_value\`] = k_block2[i1]; ctx[\`value_value\`] = k_block2[i1];
let key1 = ctx['value']; let key1 = ctx['value'];
let d1 = ctx['value']; let txt1 = ctx['value'];
let b4 = block4([d1]); let b4 = block4([txt1]);
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1; ctx[isBoundary] = 1;
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1; ctx[isBoundary] = 1;
setContextValue(ctx, \\"foo\\", 'aaa'); setContextValue(ctx, \\"foo\\", 'aaa');
let b6 = callTemplate_2.call(this, ctx, node, key + \`__1__\${key1}\`); let b6 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b7 = callTemplate_4.call(this, ctx, node, key + \`__3__\${key1}\`); let b7 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
setContextValue(ctx, \\"foo\\", 'bbb'); setContextValue(ctx, \\"foo\\", 'bbb');
let b8 = callTemplate_6.call(this, ctx, node, key + \`__5__\${key1}\`); let b8 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
let b5 = multi([b6, b7, b8]); let b5 = multi([b6, b7, b8]);
ctx[zero] = b5; ctx[zero] = b5;
let b9 = callTemplate_8.call(this, ctx, node, key + \`__7__\${key1}\`); let b9 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
ctx = ctx.__proto__; ctx = ctx.__proto__;
c_block2[i1] = withKey(multi([b4, b9]), key1); c_block2[i1] = withKey(multi([b4, b9]), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let b10 = callTemplate_10.call(this, ctx, node, key + \`__9\`); let b10 = callTemplate_5.call(this, ctx, node, key + \`__5\`);
return block1([], [b2, b10]); return block1([], [b2, b10]);
} }
}" }"
`; `;
exports[`misc global 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = withDefault(ctx['foo'], \`foo default\`);
return block1([txt1]);
}
}"
`;
exports[`misc global 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { zero } = helpers;
let block1 = createBlock(\`<año block-attribute-0=\\"falló\\"><block-child-0/></año>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = 'agüero';
let b2 = ctx[zero];
return block1([attr1], [b2]);
}
}"
`;
exports[`misc global 4`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput, withDefault } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b3 = text(\`toto default\`);
let b2 = withDefault(safeOutput(ctx['toto']), b3);
return block1([], [b2]);
}
}"
`;
exports[`misc other complex template 1`] = ` exports[`misc other complex template 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey, getTemplate } = helpers;
const callTemplate_2 = getTemplate(\`LOAD_INFOS_TEMPLATE\`); const callTemplate_1 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
let block1 = createBlock(\`<div><header><nav class=\\"navbar navbar-expand-md navbar-light bg-light\\"><a block-attribute-0=\\"href\\"><b style=\\"color:#777;\\"><block-text-1/></b></a><button type=\\"button\\" class=\\"navbar-toggler\\" data-toggle=\\"collapse\\" data-target=\\"#top_menu_collapse\\"><span class=\\"navbar-toggler-icon\\"/></button><div class=\\"collapse navbar-collapse\\" id=\\"top_menu_collapse\\" aria-expanded=\\"false\\"><ul class=\\"nav navbar-nav ml-auto text-right\\" id=\\"top_menu\\"><block-child-0/><li class=\\"nav-item divider\\"/><block-child-1/></ul><div><div class=\\"input-group input-group-sm\\"><div class=\\"input-group-prepend input-group-sm\\"><button class=\\"btn btn-default fa fa-cog\\" title=\\"Settings\\" block-handler-2=\\"click\\"/><button class=\\"btn btn-default\\" block-handler-3=\\"click\\"> More </button><block-child-2/></div><input class=\\"form-control\\" type=\\"text\\" placeholder=\\"Search\\" aria-label=\\"Search\\" name=\\"search\\" block-attribute-4=\\"value\\" block-handler-5=\\"keyup\\" block-handler-6=\\"change\\" block-ref=\\"7\\"/><div class=\\"input-group-append\\"><button class=\\"btn btn-default fa fa-eraser\\" block-handler-8=\\"click\\"/></div></div></div></div></nav></header><div class=\\"container-fluid\\" block-ref=\\"9\\"><div class=\\"row\\"><!--div class=\\"form-group col-md-6\\"> let block1 = createBlock(\`<div><header><nav class=\\"navbar navbar-expand-md navbar-light bg-light\\"><a block-attribute-0=\\"href\\"><b style=\\"color:#777;\\"><block-text-1/></b></a><button type=\\"button\\" class=\\"navbar-toggler\\" data-toggle=\\"collapse\\" data-target=\\"#top_menu_collapse\\"><span class=\\"navbar-toggler-icon\\"/></button><div class=\\"collapse navbar-collapse\\" id=\\"top_menu_collapse\\" aria-expanded=\\"false\\"><ul class=\\"nav navbar-nav ml-auto text-right\\" id=\\"top_menu\\"><block-child-0/><li class=\\"nav-item divider\\"/><block-child-1/></ul><div><div class=\\"input-group input-group-sm\\"><div class=\\"input-group-prepend input-group-sm\\"><button class=\\"btn btn-default fa fa-cog\\" title=\\"Settings\\" block-handler-2=\\"click\\"/><button class=\\"btn btn-default\\" block-handler-3=\\"click\\"> More </button><block-child-2/></div><input class=\\"form-control\\" type=\\"text\\" placeholder=\\"Search\\" aria-label=\\"Search\\" name=\\"search\\" block-attribute-4=\\"value\\" block-handler-5=\\"keyup\\" block-handler-6=\\"change\\" block-ref=\\"7\\"/><div class=\\"input-group-append\\"><button class=\\"btn btn-default fa fa-eraser\\" block-handler-8=\\"click\\"/></div></div></div></div></nav></header><div class=\\"container-fluid\\" block-ref=\\"9\\"><div class=\\"row\\"><!--div class=\\"form-group col-md-6\\">
<h5>Search options</h5> <h5>Search options</h5>
@@ -215,31 +215,33 @@ exports[`misc other complex template 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
const ref1 = (el) => refs[\`search_input\`] = el;
const ref2 = (el) => refs[\`settings_menu\`] = el;
let b2,b4,b14,b17,b22,b23,b24,b25; let b2,b4,b14,b17,b22,b23,b24,b25;
let d1 = \`/runbot/\${ctx['project'].slug}\`; let attr1 = \`/runbot/\${ctx['project'].slug}\`;
let d2 = ctx['project'].name; let txt1 = ctx['project'].name;
ctx = Object.create(ctx); ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']); const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['projects']);
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`project\`] = v_block2[i1]; ctx[\`project\`] = v_block2[i1];
let key1 = ctx['project'].id; let key1 = ctx['project'].id;
let d3 = [ctx['selectProject'](ctx['project']), ctx]; let hdlr1 = [ctx['selectProject'](ctx['project']), ctx];
let d4 = ctx['project'].name; let txt2 = ctx['project'].name;
c_block2[i1] = withKey(block3([d3, d4]), key1); c_block2[i1] = withKey(block3([hdlr1, txt2]), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
b2 = list(c_block2); b2 = list(c_block2);
if (ctx['user']) { if (ctx['user']) {
let b5,b6; let b5,b6;
if (ctx['user'].public) { if (ctx['user'].public) {
let d5 = \`/web/login?redirect=/\`; let attr2 = \`/web/login?redirect=/\`;
b5 = block5([d5]); b5 = block5([attr2]);
} else { } else {
let b7,b10,b13; let b7,b10,b13;
if (ctx['nb_assigned_errors']&&ctx['nb_assigned_errors']>0) { if (ctx['nb_assigned_errors']&&ctx['nb_assigned_errors']>0) {
let d6 = \`You have \${ctx['nb_assigned_errors']} random bug assigned\`; let attr3 = \`You have \${ctx['nb_assigned_errors']} random bug assigned\`;
let d7 = ctx['nb_assigned_errors']; let txt3 = ctx['nb_assigned_errors'];
let b8 = block8([d6, d7]); let b8 = block8([attr3, txt3]);
let b9 = block9(); let b9 = block9();
b7 = multi([b8, b9]); b7 = multi([b8, b9]);
} else if (ctx['nb_build_errors']&&ctx['nb_build_errors']>0) { } else if (ctx['nb_build_errors']&&ctx['nb_build_errors']>0) {
@@ -247,37 +249,35 @@ exports[`misc other complex template 1`] = `
let b12 = block12(); let b12 = block12();
b10 = multi([b11, b12]); b10 = multi([b11, b12]);
} }
let d8 = ctx['user'].name.length>25?ctx['user'].namesubstring(0,23)+'...':ctx['user'].name; let txt4 = ctx['user'].name.length>25?ctx['user'].namesubstring(0,23)+'...':ctx['user'].name;
let d9 = \`/web/session/logout?redirect=/\`; let attr4 = \`/web/session/logout?redirect=/\`;
let d10 = \`/web\`; let attr5 = \`/web\`;
b13 = block13([d8, d9, d10]); b13 = block13([txt4, attr4, attr5]);
b6 = multi([b7, b10, b13]); b6 = multi([b7, b10, b13]);
} }
b4 = multi([b5, b6]); b4 = multi([b5, b6]);
} }
let d11 = [ctx['toggleSettingsMenu'], ctx]; let hdlr2 = [ctx['toggleSettingsMenu'], ctx];
let d12 = [ctx['toggleMore'], ctx]; let hdlr3 = [ctx['toggleMore'], ctx];
if (ctx['categories']&&ctx['categories'].length>1) { if (ctx['categories']&&ctx['categories'].length>1) {
ctx = Object.create(ctx); ctx = Object.create(ctx);
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']); const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);
for (let i1 = 0; i1 < l_block15; i1++) { for (let i1 = 0; i1 < l_block15; i1++) {
ctx[\`category\`] = v_block15[i1]; ctx[\`category\`] = v_block15[i1];
let key1 = ctx['category'].id; let key1 = ctx['category'].id;
let d13 = ctx['category'].id; let attr6 = ctx['category'].id;
let d14 = ctx['category'].id==ctx['options'].active_category_id; let attr7 = ctx['category'].id==ctx['options'].active_category_id;
let d15 = ctx['category'].name; let txt5 = ctx['category'].name;
c_block15[i1] = withKey(block16([d13, d14, d15]), key1); c_block15[i1] = withKey(block16([attr6, attr7, txt5]), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b15 = list(c_block15); let b15 = list(c_block15);
b14 = block14([], [b15]); b14 = block14([], [b15]);
} }
let d16 = ctx['search'].value; let attr8 = ctx['search'].value;
let d17 = [ctx['updateFilter'], ctx]; let hdlr4 = [ctx['updateFilter'], ctx];
let d18 = [ctx['updateFilter'], ctx]; let hdlr5 = [ctx['updateFilter'], ctx];
let d19 = (el) => refs[\`search_input\`] = el; let hdlr6 = [ctx['clearSearch'], ctx];
let d20 = [ctx['clearSearch'], ctx];
let d21 = (el) => refs[\`settings_menu\`] = el;
if (ctx['triggers']) { if (ctx['triggers']) {
ctx = Object.create(ctx); ctx = Object.create(ctx);
const [k_block18, v_block18, l_block18, c_block18] = prepareList(ctx['triggers']); const [k_block18, v_block18, l_block18, c_block18] = prepareList(ctx['triggers']);
@@ -286,41 +286,41 @@ exports[`misc other complex template 1`] = `
let key1 = ctx['trigger'].id; let key1 = ctx['trigger'].id;
let b20; let b20;
if (!ctx['trigger'].manual&&ctx['trigger'].project_id===ctx['project'].id&&ctx['trigger'].category_id===ctx['options'].active_category_id) { if (!ctx['trigger'].manual&&ctx['trigger'].project_id===ctx['project'].id&&ctx['trigger'].category_id===ctx['options'].active_category_id) {
let d22 = \`trigger_\${ctx['trigger'].id}\`; let attr9 = \`trigger_\${ctx['trigger'].id}\`;
let d23 = \`trigger_\${ctx['trigger'].id}\`; let attr10 = \`trigger_\${ctx['trigger'].id}\`;
let d24 = ctx['options'].trigger_display[ctx['trigger'].id]; let attr11 = ctx['options'].trigger_display[ctx['trigger'].id];
let d25 = ctx['trigger'].id; let attr12 = ctx['trigger'].id;
let d26 = [ctx['updateTriggerDisplay'], ctx]; let hdlr7 = [ctx['updateTriggerDisplay'], ctx];
let d27 = \`trigger_\${ctx['trigger'].id}\`; let attr13 = \`trigger_\${ctx['trigger'].id}\`;
let d28 = ctx['trigger'].name; let txt6 = ctx['trigger'].name;
b20 = block20([d22, d23, d24, d25, d26, d27, d28]); b20 = block20([attr9, attr10, attr11, attr12, hdlr7, attr13, txt6]);
} }
c_block18[i1] = withKey(multi([b20]), key1); c_block18[i1] = withKey(multi([b20]), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b18 = list(c_block18); let b18 = list(c_block18);
let d29 = [ctx['triggerAll'], ctx]; let hdlr8 = [ctx['triggerAll'], ctx];
let d30 = [ctx['triggerNone'], ctx]; let hdlr9 = [ctx['triggerNone'], ctx];
let d31 = [ctx['triggerDefault'], ctx]; let hdlr10 = [ctx['triggerDefault'], ctx];
let d32 = [ctx['toggleSettingsMenu'], ctx]; let hdlr11 = [ctx['toggleSettingsMenu'], ctx];
let b21 = block21([d29, d30, d31, d32]); let b21 = block21([hdlr8, hdlr9, hdlr10, hdlr11]);
b17 = multi([b18, b21]); b17 = multi([b18, b21]);
} }
if (ctx['load_infos']) { if (ctx['load_infos']) {
b22 = callTemplate_2.call(this, ctx, node, key + \`__1\`); b22 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
} }
if (ctx['message']) { if (ctx['message']) {
let d33 = ctx['message']; let txt7 = ctx['message'];
b23 = block23([d33]); b23 = block23([txt7]);
} }
if (!ctx['project']) { if (!ctx['project']) {
b24 = block24(); b24 = block24();
} else { } else {
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__3\`, node, ctx); let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky, category_custom_views: ctx['category_custom_views'], search: ctx['search']}, key + \`__2\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__4\`, node, ctx); let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev, search: ctx['search']}, key + \`__3\`, node, ctx);
b25 = block25([], [b26, b27]); b25 = block25([], [b26, b27]);
} }
return block1([d1, d2, d11, d12, d16, d17, d18, d19, d20, d21], [b2, b4, b14, b17, b22, b23, b24, b25]); return block1([attr1, txt1, hdlr2, hdlr3, attr8, hdlr4, hdlr5, ref1, hdlr6, ref2], [b2, b4, b14, b17, b22, b23, b24, b25]);
} }
}" }"
`; `;
@@ -3,8 +3,8 @@
exports[`memory t-foreach does not leak stuff in global scope 1`] = ` exports[`memory t-foreach does not leak stuff in global scope 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<p><block-child-0/></p>\`); let block1 = createBlock(\`<p><block-child-0/></p>\`);
@@ -3,8 +3,7 @@
exports[`simple templates, mostly static can render a table row 1`] = ` exports[`simple templates, mostly static can render a table row 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<tr><td>cell</td></tr>\`); let block1 = createBlock(\`<tr><td>cell</td></tr>\`);
@@ -17,8 +16,7 @@ exports[`simple templates, mostly static can render a table row 1`] = `
exports[`simple templates, mostly static div with a class attribute 1`] = ` exports[`simple templates, mostly static div with a class attribute 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"abc\\">foo</div>\`); let block1 = createBlock(\`<div class=\\"abc\\">foo</div>\`);
@@ -31,8 +29,7 @@ exports[`simple templates, mostly static div with a class attribute 1`] = `
exports[`simple templates, mostly static div with a class attribute with a quote 1`] = ` exports[`simple templates, mostly static div with a class attribute with a quote 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div class=\\"a'bc\\">word</div>\`); let block1 = createBlock(\`<div class=\\"a'bc\\">word</div>\`);
@@ -45,8 +42,7 @@ exports[`simple templates, mostly static div with a class attribute with a quote
exports[`simple templates, mostly static div with a span child node 1`] = ` exports[`simple templates, mostly static div with a span child node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><span>word</span></div>\`); let block1 = createBlock(\`<div><span>word</span></div>\`);
@@ -59,8 +55,7 @@ exports[`simple templates, mostly static div with a span child node 1`] = `
exports[`simple templates, mostly static div with an arbitrary attribute with a quote 1`] = ` exports[`simple templates, mostly static div with an arbitrary attribute with a quote 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div abc=\\"a'bc\\">word</div>\`); let block1 = createBlock(\`<div abc=\\"a'bc\\">word</div>\`);
@@ -73,8 +68,7 @@ exports[`simple templates, mostly static div with an arbitrary attribute with a
exports[`simple templates, mostly static div with an empty class attribute 1`] = ` exports[`simple templates, mostly static div with an empty class attribute 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>word</div>\`); let block1 = createBlock(\`<div>word</div>\`);
@@ -87,8 +81,7 @@ exports[`simple templates, mostly static div with an empty class attribute 1`] =
exports[`simple templates, mostly static div with content 1`] = ` exports[`simple templates, mostly static div with content 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>foo</div>\`); let block1 = createBlock(\`<div>foo</div>\`);
@@ -101,14 +94,13 @@ exports[`simple templates, mostly static div with content 1`] = `
exports[`simple templates, mostly static dom node with t-esc 1`] = ` exports[`simple templates, mostly static dom node with t-esc 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['text']; let txt1 = ctx['text'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -116,14 +108,13 @@ exports[`simple templates, mostly static dom node with t-esc 1`] = `
exports[`simple templates, mostly static dom node with t-esc 2`] = ` exports[`simple templates, mostly static dom node with t-esc 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['text']; let txt1 = ctx['text'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -131,8 +122,7 @@ exports[`simple templates, mostly static dom node with t-esc 2`] = `
exports[`simple templates, mostly static dynamic text value 1`] = ` exports[`simple templates, mostly static dynamic text value 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(ctx['text']); return text(ctx['text']);
@@ -143,8 +133,7 @@ exports[`simple templates, mostly static dynamic text value 1`] = `
exports[`simple templates, mostly static empty div 1`] = ` exports[`simple templates, mostly static empty div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div/>\`); let block1 = createBlock(\`<div/>\`);
@@ -157,8 +146,7 @@ exports[`simple templates, mostly static empty div 1`] = `
exports[`simple templates, mostly static empty string 1`] = ` exports[`simple templates, mostly static empty string 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(\`\`); return text(\`\`);
@@ -169,8 +157,7 @@ exports[`simple templates, mostly static empty string 1`] = `
exports[`simple templates, mostly static empty string in a template set 1`] = ` exports[`simple templates, mostly static empty string in a template set 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(\`\`); return text(\`\`);
@@ -181,8 +168,7 @@ exports[`simple templates, mostly static empty string in a template set 1`] = `
exports[`simple templates, mostly static inline template string in t-esc 1`] = ` exports[`simple templates, mostly static inline template string in t-esc 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(\`text\`); return text(\`text\`);
@@ -193,8 +179,8 @@ exports[`simple templates, mostly static inline template string in t-esc 1`] = `
exports[`simple templates, mostly static inline template string with content in t-esc 1`] = ` exports[`simple templates, mostly static inline template string with content in t-esc 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -208,8 +194,7 @@ exports[`simple templates, mostly static inline template string with content in
exports[`simple templates, mostly static inline template string with variable in context 1`] = ` exports[`simple templates, mostly static inline template string with variable in context 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(\`text \${ctx['v']}\`); return text(\`text \${ctx['v']}\`);
@@ -220,8 +205,7 @@ exports[`simple templates, mostly static inline template string with variable in
exports[`simple templates, mostly static multiple root nodes 1`] = ` exports[`simple templates, mostly static multiple root nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block2 = createBlock(\`<div>foo</div>\`); let block2 = createBlock(\`<div>foo</div>\`);
let block3 = createBlock(\`<span>hey</span>\`); let block3 = createBlock(\`<span>hey</span>\`);
@@ -237,8 +221,7 @@ exports[`simple templates, mostly static multiple root nodes 1`] = `
exports[`simple templates, mostly static simple string 1`] = ` exports[`simple templates, mostly static simple string 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(\`hello vdom\`); return text(\`hello vdom\`);
@@ -249,8 +232,7 @@ exports[`simple templates, mostly static simple string 1`] = `
exports[`simple templates, mostly static simple string in t tag 1`] = ` exports[`simple templates, mostly static simple string in t tag 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return text(\`hello vdom\`); return text(\`hello vdom\`);
@@ -261,8 +243,7 @@ exports[`simple templates, mostly static simple string in t tag 1`] = `
exports[`simple templates, mostly static static text and dynamic text (no t tag) 1`] = ` exports[`simple templates, mostly static static text and dynamic text (no t tag) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = text(\`hello \`); let b2 = text(\`hello \`);
@@ -275,8 +256,7 @@ exports[`simple templates, mostly static static text and dynamic text (no t tag)
exports[`simple templates, mostly static static text and dynamic text 1`] = ` exports[`simple templates, mostly static static text and dynamic text 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = text(\`hello \`); let b2 = text(\`hello \`);
@@ -289,14 +269,13 @@ exports[`simple templates, mostly static static text and dynamic text 1`] = `
exports[`simple templates, mostly static t-esc in dom node 1`] = ` exports[`simple templates, mostly static t-esc in dom node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['text']; let txt1 = ctx['text'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -304,14 +283,13 @@ exports[`simple templates, mostly static t-esc in dom node 1`] = `
exports[`simple templates, mostly static t-esc in dom node, variations 1`] = ` exports[`simple templates, mostly static t-esc in dom node, variations 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>hello <block-text-0/></div>\`); let block1 = createBlock(\`<div>hello <block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['text']; let txt1 = ctx['text'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -319,14 +297,46 @@ exports[`simple templates, mostly static t-esc in dom node, variations 1`] = `
exports[`simple templates, mostly static t-esc in dom node, variations 2`] = ` exports[`simple templates, mostly static t-esc in dom node, variations 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>hello <block-text-0/> world</div>\`); let block1 = createBlock(\`<div>hello <block-text-0/> world</div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['text']; let txt1 = ctx['text'];
return block1([d1]); return block1([txt1]);
}
}"
`;
exports[`simple templates, mostly static template with multiple t tag with multiple content 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/><block-text-1/>Loading<block-text-2/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['a'];
let txt2 = ctx['b'];
let txt3 = ctx['c'];
return block1([txt1, txt2, txt3]);
}
}"
`;
exports[`simple templates, mostly static template with t tag with multiple content 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div>Loading<block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (false) {
b2 = text(\`\`);
}
return block1([], [b2]);
} }
}" }"
`; `;
@@ -334,8 +344,7 @@ exports[`simple templates, mostly static t-esc in dom node, variations 2`] = `
exports[`simple templates, mostly static two t-escs next to each other 1`] = ` exports[`simple templates, mostly static two t-escs next to each other 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['text1']); let b2 = text(ctx['text1']);
@@ -348,8 +357,7 @@ exports[`simple templates, mostly static two t-escs next to each other 1`] = `
exports[`simple templates, mostly static two t-escs next to each other 2`] = ` exports[`simple templates, mostly static two t-escs next to each other 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['text1']); let b2 = text(ctx['text1']);
@@ -362,15 +370,14 @@ exports[`simple templates, mostly static two t-escs next to each other 2`] = `
exports[`simple templates, mostly static two t-escs next to each other, in a div 1`] = ` exports[`simple templates, mostly static two t-escs next to each other, in a div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`); let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['text1']; let txt1 = ctx['text1'];
let d2 = ctx['text2']; let txt2 = ctx['text2'];
return block1([d1, d2]); return block1([txt1, txt2]);
} }
}" }"
`; `;
+87 -9
View File
@@ -3,8 +3,7 @@
exports[`properly support svg add proper namespace to g tags 1`] = ` exports[`properly support svg add proper namespace to g tags 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`); let block1 = createBlock(\`<g block-ns=\\"http://www.w3.org/2000/svg\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </g>\`);
@@ -17,8 +16,7 @@ exports[`properly support svg add proper namespace to g tags 1`] = `
exports[`properly support svg add proper namespace to svg 1`] = ` exports[`properly support svg add proper namespace to svg 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`); let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\" width=\\"100px\\" height=\\"90px\\"><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/> </svg>\`);
@@ -31,8 +29,7 @@ exports[`properly support svg add proper namespace to svg 1`] = `
exports[`properly support svg namespace to g tags not added if already in svg namespace 1`] = ` exports[`properly support svg namespace to g tags not added if already in svg namespace 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`); let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><g/></svg>\`);
@@ -45,13 +42,94 @@ exports[`properly support svg namespace to g tags not added if already in svg na
exports[`properly support svg namespace to svg tags added even if already in svg namespace 1`] = ` exports[`properly support svg namespace to svg tags added even if already in svg namespace 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg block-ns=\\"http://www.w3.org/2000/svg\\"/></svg>\`); let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><svg/></svg>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return block1(); return block1();
} }
}" }"
`; `;
exports[`properly support svg svg creates new block if it is within html -- 2 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/><block-child-0/></svg>\`);
let block3 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let b3;
if (ctx['hasPath']) {
b3 = block3();
}
let b2 = block2([], [b3]);
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg creates new block if it is within html 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><polygon fill=\\"#000000\\" points=\\"0 0 4 4 8 0\\" transform=\\"translate(5 7)\\"/></svg>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = block2();
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg namespace added to sub templates if root tag is path 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`path\`);
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
exports[`properly support svg svg namespace added to sub templates if root tag is path 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`properly support svg svg namespace added to sub-blocks 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<svg block-ns=\\"http://www.w3.org/2000/svg\\"><block-child-0/></svg>\`);
let block2 = createBlock(\`<path block-ns=\\"http://www.w3.org/2000/svg\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['path']) {
b2 = block2();
}
return block1([], [b2]);
}
}"
`;
File diff suppressed because it is too large Load Diff
@@ -3,8 +3,7 @@
exports[`debugging t-debug 1`] = ` exports[`debugging t-debug 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span>hey</span>\`); let block2 = createBlock(\`<span>hey</span>\`);
@@ -24,8 +23,7 @@ exports[`debugging t-debug 1`] = `
exports[`debugging t-debug on sub template 1`] = ` exports[`debugging t-debug on sub template 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<p>coucou</p>\`); let block1 = createBlock(\`<p>coucou</p>\`);
@@ -39,14 +37,14 @@ exports[`debugging t-debug on sub template 1`] = `
exports[`debugging t-debug on sub template 2`] = ` exports[`debugging t-debug on sub template 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_2 = getTemplate(\`sub\`); const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = callTemplate_2.call(this, ctx, node, key + \`__1\`); let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -55,8 +53,8 @@ exports[`debugging t-debug on sub template 2`] = `
exports[`debugging t-log 1`] = ` exports[`debugging t-log 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div/>\`); let block1 = createBlock(\`<div/>\`);
+59 -63
View File
@@ -3,18 +3,17 @@
exports[`t-esc div with falsy values 1`] = ` exports[`t-esc div with falsy values 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><p><block-text-0/></p><p><block-text-1/></p><p><block-text-2/></p><p><block-text-3/></p><p><block-text-4/></p></div>\`); let block1 = createBlock(\`<div><p><block-text-0/></p><p><block-text-1/></p><p><block-text-2/></p><p><block-text-3/></p><p><block-text-4/></p></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['v1']; let txt1 = ctx['v1'];
let d2 = ctx['v2']; let txt2 = ctx['v2'];
let d3 = ctx['v3']; let txt3 = ctx['v3'];
let d4 = ctx['v4']; let txt4 = ctx['v4'];
let d5 = ctx['v5']; let txt5 = ctx['v5'];
return block1([d1, d2, d3, d4, d5]); return block1([txt1, txt2, txt3, txt4, txt5]);
} }
}" }"
`; `;
@@ -22,14 +21,13 @@ exports[`t-esc div with falsy values 1`] = `
exports[`t-esc escaping 1`] = ` exports[`t-esc escaping 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['var']; let txt1 = ctx['var'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -37,14 +35,13 @@ exports[`t-esc escaping 1`] = `
exports[`t-esc escaping on a node 1`] = ` exports[`t-esc escaping on a node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = 'ok'; let txt1 = 'ok';
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -52,14 +49,14 @@ exports[`t-esc escaping on a node 1`] = `
exports[`t-esc escaping on a node with a body 1`] = ` exports[`t-esc escaping on a node with a body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = withDefault('ok', \`nope\`); let txt1 = withDefault('ok', \`nope\`);
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -67,14 +64,14 @@ exports[`t-esc escaping on a node with a body 1`] = `
exports[`t-esc escaping on a node with a body, as a default 1`] = ` exports[`t-esc escaping on a node with a body, as a default 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { withDefault } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = withDefault(ctx['var'], \`nope\`); let txt1 = withDefault(ctx['var'], \`nope\`);
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -82,8 +79,7 @@ exports[`t-esc escaping on a node with a body, as a default 1`] = `
exports[`t-esc falsy values in text nodes 1`] = ` exports[`t-esc falsy values in text nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = text(ctx['v1']); let b2 = text(ctx['v1']);
@@ -103,14 +99,13 @@ exports[`t-esc falsy values in text nodes 1`] = `
exports[`t-esc literal 1`] = ` exports[`t-esc literal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = 'ok'; let txt1 = 'ok';
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -118,19 +113,22 @@ exports[`t-esc literal 1`] = `
exports[`t-esc t-esc is escaped 1`] = ` exports[`t-esc t-esc is escaped 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, LazyValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
let block2 = createBlock(\`<p>escaped</p>\`); let block2 = createBlock(\`<p>escaped</p>\`);
function value1(ctx, node, key = \\"\\") {
return block2();
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b2 = block2(); ctx[\`var\`] = new LazyValue(value1, ctx, node);
ctx[\`var\`] = b2; let txt1 = ctx['var'];
let d1 = ctx['var']; return block1([txt1]);
return block1([d1]);
} }
}" }"
`; `;
@@ -138,14 +136,13 @@ exports[`t-esc t-esc is escaped 1`] = `
exports[`t-esc t-esc work with spread operator 1`] = ` exports[`t-esc t-esc work with spread operator 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = [...ctx['state'].list]; let txt1 = [...ctx['state'].list];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -153,24 +150,9 @@ exports[`t-esc t-esc work with spread operator 1`] = `
exports[`t-esc t-esc=0 is escaped 1`] = ` exports[`t-esc t-esc=0 is escaped 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, zero, getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx[zero];
return block1([d1]);
}
}"
`;
exports[`t-esc t-esc=0 is escaped 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<p>escaped</p>\`); let block2 = createBlock(\`<p>escaped</p>\`);
@@ -180,23 +162,37 @@ exports[`t-esc t-esc=0 is escaped 2`] = `
ctx[isBoundary] = 1; ctx[isBoundary] = 1;
let b2 = block2(); let b2 = block2();
ctx[zero] = b2; ctx[zero] = b2;
let b3 = callTemplate_2.call(this, ctx, node, key + \`__1\`); let b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]); return block1([], [b3]);
} }
}" }"
`; `;
exports[`t-esc t-esc=0 is escaped 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { zero } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx[zero];
return block1([txt1]);
}
}"
`;
exports[`t-esc variable 1`] = ` exports[`t-esc variable 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`); let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let d1 = ctx['var']; let txt1 = ctx['var'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -3,8 +3,8 @@
exports[`t-foreach does not pollute the rendering context 1`] = ` exports[`t-foreach does not pollute the rendering context 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -25,8 +25,8 @@ exports[`t-foreach does not pollute the rendering context 1`] = `
exports[`t-foreach iterate on items (on a element node) 1`] = ` exports[`t-foreach iterate on items (on a element node) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span><block-text-0/></span>\`); let block3 = createBlock(\`<span><block-text-0/></span>\`);
@@ -37,8 +37,8 @@ exports[`t-foreach iterate on items (on a element node) 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`item\`] = v_block2[i1]; ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item']; let key1 = ctx['item'];
let d1 = ctx['item']; let txt1 = ctx['item'];
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([txt1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
@@ -49,8 +49,8 @@ exports[`t-foreach iterate on items (on a element node) 1`] = `
exports[`t-foreach iterate on items 1`] = ` exports[`t-foreach iterate on items 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -80,8 +80,8 @@ exports[`t-foreach iterate on items 1`] = `
exports[`t-foreach iterate, dict param 1`] = ` exports[`t-foreach iterate, dict param 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -111,8 +111,8 @@ exports[`t-foreach iterate, dict param 1`] = `
exports[`t-foreach iterate, position 1`] = ` exports[`t-foreach iterate, position 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -147,8 +147,8 @@ exports[`t-foreach iterate, position 1`] = `
exports[`t-foreach simple iteration (in a node) 1`] = ` exports[`t-foreach simple iteration (in a node) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -169,8 +169,8 @@ exports[`t-foreach simple iteration (in a node) 1`] = `
exports[`t-foreach simple iteration 1`] = ` exports[`t-foreach simple iteration 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -188,8 +188,8 @@ exports[`t-foreach simple iteration 1`] = `
exports[`t-foreach simple iteration with two nodes inside 1`] = ` exports[`t-foreach simple iteration with two nodes inside 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block3 = createBlock(\`<span>a<block-text-0/></span>\`); let block3 = createBlock(\`<span>a<block-text-0/></span>\`);
let block4 = createBlock(\`<span>b<block-text-0/></span>\`); let block4 = createBlock(\`<span>b<block-text-0/></span>\`);
@@ -200,10 +200,10 @@ exports[`t-foreach simple iteration with two nodes inside 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) { for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1]; ctx[\`item\`] = v_block1[i1];
let key1 = ctx['item']; let key1 = ctx['item'];
let d1 = ctx['item']; let txt1 = ctx['item'];
let b3 = block3([d1]); let b3 = block3([txt1]);
let d2 = ctx['item']; let txt2 = ctx['item'];
let b4 = block4([d2]); let b4 = block4([txt2]);
c_block1[i1] = withKey(multi([b3, b4]), key1); c_block1[i1] = withKey(multi([b3, b4]), key1);
} }
return list(c_block1); return list(c_block1);
@@ -214,28 +214,9 @@ exports[`t-foreach simple iteration with two nodes inside 1`] = `
exports[`t-foreach t-call with body in t-foreach in t-foreach 1`] = ` exports[`t-foreach t-call with body in t-foreach in t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, isBoundary, withDefault, setContextValue, getTemplate, withKey } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
return function template(ctx, node, key = \\"\\") {
let b2 = text(\` [\`);
let b3 = text(ctx['a']);
let b4 = text(\`] [\`);
let b5 = text(ctx['b']);
let b6 = text(\`] [\`);
let b7 = text(ctx['c']);
let b8 = text(\`] \`);
return multi([b2, b3, b4, b5, b6, b7, b8]);
}
}"
`;
exports[`t-foreach t-call with body in t-foreach in t-foreach 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/><span>[<block-text-0/>][<block-text-1/>][<block-text-2/>]</span></div>\`); let block1 = createBlock(\`<div><block-child-0/><span>[<block-text-0/>][<block-text-1/>][<block-text-2/>]</span></div>\`);
let block6 = createBlock(\`<span><block-text-0/></span>\`); let block6 = createBlock(\`<span><block-text-0/></span>\`);
@@ -264,35 +245,31 @@ exports[`t-foreach t-call with body in t-foreach in t-foreach 2`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1; ctx[isBoundary] = 1;
setContextValue(ctx, \\"c\\", 'x'+'_'+ctx['a']+'_'+ctx['b']); setContextValue(ctx, \\"c\\", 'x'+'_'+ctx['a']+'_'+ctx['b']);
c_block4[i2] = withKey(callTemplate_2.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2); c_block4[i2] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2);
ctx = ctx.__proto__; ctx = ctx.__proto__;
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b4 = list(c_block4); let b4 = list(c_block4);
let d1 = ctx['c']; let txt1 = ctx['c'];
let b6 = block6([d1]); let b6 = block6([txt1]);
c_block2[i1] = withKey(multi([b4, b6]), key1); c_block2[i1] = withKey(multi([b4, b6]), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let d2 = ctx['a']; let txt2 = ctx['a'];
let d3 = ctx['b']; let txt3 = ctx['b'];
let d4 = ctx['c']; let txt4 = ctx['c'];
return block1([d2, d3, d4], [b2]); return block1([txt2, txt3, txt4], [b2]);
} }
}" }"
`; `;
exports[`t-foreach t-call without body in t-foreach in t-foreach 1`] = ` exports[`t-foreach t-call with body in t-foreach in t-foreach 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"c\\", 'x'+'_'+ctx['a']+'_'+ctx['b']);
let b2 = text(\` [\`); let b2 = text(\` [\`);
let b3 = text(ctx['a']); let b3 = text(ctx['a']);
let b4 = text(\`] [\`); let b4 = text(\`] [\`);
@@ -305,12 +282,12 @@ exports[`t-foreach t-call without body in t-foreach in t-foreach 1`] = `
}" }"
`; `;
exports[`t-foreach t-call without body in t-foreach in t-foreach 2`] = ` exports[`t-foreach t-call without body in t-foreach in t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, getTemplate, withKey } = helpers;
const callTemplate_2 = getTemplate(\`sub\`); const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/><span>[<block-text-0/>][<block-text-1/>][<block-text-2/>]</span></div>\`); let block1 = createBlock(\`<div><block-child-0/><span>[<block-text-0/>][<block-text-1/>][<block-text-2/>]</span></div>\`);
let block6 = createBlock(\`<span><block-text-0/></span>\`); let block6 = createBlock(\`<span><block-text-0/></span>\`);
@@ -334,20 +311,42 @@ exports[`t-foreach t-call without body in t-foreach in t-foreach 2`] = `
ctx[\`b_index\`] = i2; ctx[\`b_index\`] = i2;
ctx[\`b_value\`] = k_block4[i2]; ctx[\`b_value\`] = k_block4[i2];
let key2 = ctx['b']; let key2 = ctx['b'];
c_block4[i2] = withKey(callTemplate_2.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2); c_block4[i2] = withKey(callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}__\${key2}\`), key2);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b4 = list(c_block4); let b4 = list(c_block4);
let d1 = ctx['c']; let txt1 = ctx['c'];
let b6 = block6([d1]); let b6 = block6([txt1]);
c_block2[i1] = withKey(multi([b4, b6]), key1); c_block2[i1] = withKey(multi([b4, b6]), key1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let d2 = ctx['a']; let txt2 = ctx['a'];
let d3 = ctx['b']; let txt3 = ctx['b'];
let d4 = ctx['c']; let txt4 = ctx['c'];
return block1([d2, d3, d4], [b2]); return block1([txt2, txt3, txt4], [b2]);
}
}"
`;
exports[`t-foreach t-call without body in t-foreach in t-foreach 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { isBoundary, withDefault, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"c\\", 'x'+'_'+ctx['a']+'_'+ctx['b']);
let b2 = text(\` [\`);
let b3 = text(ctx['a']);
let b4 = text(\`] [\`);
let b5 = text(ctx['b']);
let b6 = text(\`] [\`);
let b7 = text(ctx['c']);
let b8 = text(\`] \`);
return multi([b2, b3, b4, b5, b6, b7, b8]);
} }
}" }"
`; `;
@@ -355,8 +354,8 @@ exports[`t-foreach t-call without body in t-foreach in t-foreach 2`] = `
exports[`t-foreach t-foreach in t-foreach 1`] = ` exports[`t-foreach t-foreach in t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -389,8 +388,8 @@ exports[`t-foreach t-foreach in t-foreach 1`] = `
exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = ` exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block3 = createBlock(\`<span><block-text-0/></span>\`); let block3 = createBlock(\`<span><block-text-0/></span>\`);
@@ -402,8 +401,8 @@ exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = `
let key1 = ctx['elem'].id; let key1 = ctx['elem'].id;
let b3; let b3;
if (ctx['elem'].id<3) { if (ctx['elem'].id<3) {
let d1 = ctx['elem'].text; let txt1 = ctx['elem'].text;
b3 = block3([d1]); b3 = block3([txt1]);
} }
c_block1[i1] = withKey(multi([b3]), key1); c_block1[i1] = withKey(multi([b3]), key1);
} }
@@ -415,8 +414,8 @@ exports[`t-foreach t-foreach with t-if inside (no external node) 1`] = `
exports[`t-foreach t-foreach with t-if inside 1`] = ` exports[`t-foreach t-foreach with t-if inside 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block4 = createBlock(\`<span><block-text-0/></span>\`); let block4 = createBlock(\`<span><block-text-0/></span>\`);
@@ -429,8 +428,8 @@ exports[`t-foreach t-foreach with t-if inside 1`] = `
let key1 = ctx['elem'].id; let key1 = ctx['elem'].id;
let b4; let b4;
if (ctx['elem'].id<3) { if (ctx['elem'].id<3) {
let d1 = ctx['elem'].text; let txt1 = ctx['elem'].text;
b4 = block4([d1]); b4 = block4([txt1]);
} }
c_block2[i1] = withKey(multi([b4]), key1); c_block2[i1] = withKey(multi([b4]), key1);
} }
@@ -443,8 +442,8 @@ exports[`t-foreach t-foreach with t-if inside 1`] = `
exports[`t-foreach t-key on t-foreach 1`] = ` exports[`t-foreach t-key on t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span/>\`); let block3 = createBlock(\`<span/>\`);
@@ -466,8 +465,8 @@ exports[`t-foreach t-key on t-foreach 1`] = `
exports[`t-foreach throws error if invalid loop expression 1`] = ` exports[`t-foreach throws error if invalid loop expression 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span/>\`); let block3 = createBlock(\`<span/>\`);
@@ -491,9 +490,8 @@ exports[`t-foreach throws error if invalid loop expression 1`] = `
exports[`t-foreach with t-memo 1`] = ` exports[`t-foreach with t-memo 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let assign = Object.assign;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<p><block-text-0/><block-text-1/></p>\`); let block3 = createBlock(\`<p><block-text-0/><block-text-1/></p>\`);
@@ -515,10 +513,10 @@ exports[`t-foreach with t-memo 1`] = `
continue; continue;
} }
} }
let d1 = ctx['item'].x; let txt1 = ctx['item'].x;
let d2 = ctx['item'].y; let txt2 = ctx['item'].y;
c_block2[i1] = withKey(block3([d1, d2]), key1); c_block2[i1] = withKey(block3([txt1, txt2]), key1);
nextCache[key1] = assign(c_block2[i1], {memo: memo1}); nextCache[key1] = Object.assign(c_block2[i1], {memo: memo1});
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
+48 -54
View File
@@ -3,8 +3,7 @@
exports[`t-if a t-if next to a div 1`] = ` exports[`t-if a t-if next to a div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block2 = createBlock(\`<div>foo</div>\`); let block2 = createBlock(\`<div>foo</div>\`);
@@ -22,8 +21,7 @@ exports[`t-if a t-if next to a div 1`] = `
exports[`t-if a t-if with two inner nodes 1`] = ` exports[`t-if a t-if with two inner nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block3 = createBlock(\`<span>yip</span>\`); let block3 = createBlock(\`<span>yip</span>\`);
let block4 = createBlock(\`<div>yip</div>\`); let block4 = createBlock(\`<div>yip</div>\`);
@@ -43,8 +41,7 @@ exports[`t-if a t-if with two inner nodes 1`] = `
exports[`t-if boolean value condition elif (no outside node) 1`] = ` exports[`t-if boolean value condition elif (no outside node) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3,b4,b5; let b2,b3,b4,b5;
@@ -65,8 +62,7 @@ exports[`t-if boolean value condition elif (no outside node) 1`] = `
exports[`t-if boolean value condition elif 1`] = ` exports[`t-if boolean value condition elif 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-2/><block-child-3/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-2/><block-child-3/></div>\`);
@@ -89,8 +85,7 @@ exports[`t-if boolean value condition elif 1`] = `
exports[`t-if boolean value condition else 1`] = ` exports[`t-if boolean value condition else 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><span>begin</span><block-child-0/><block-child-1/><span>end</span></div>\`); let block1 = createBlock(\`<div><span>begin</span><block-child-0/><block-child-1/><span>end</span></div>\`);
@@ -109,8 +104,7 @@ exports[`t-if boolean value condition else 1`] = `
exports[`t-if boolean value condition false else 1`] = ` exports[`t-if boolean value condition false else 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><span>begin</span><block-child-0/><block-child-1/><span>end</span></div>\`); let block1 = createBlock(\`<div><span>begin</span><block-child-0/><block-child-1/><span>end</span></div>\`);
@@ -129,8 +123,7 @@ exports[`t-if boolean value condition false else 1`] = `
exports[`t-if boolean value condition missing 1`] = ` exports[`t-if boolean value condition missing 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -147,8 +140,7 @@ exports[`t-if boolean value condition missing 1`] = `
exports[`t-if can use some boolean operators in expressions 1`] = ` exports[`t-if can use some boolean operators in expressions 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-2/><block-child-3/><block-child-4/><block-child-5/><block-child-6/><block-child-7/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/><block-child-2/><block-child-3/><block-child-4/><block-child-5/><block-child-6/><block-child-7/></div>\`);
@@ -186,8 +178,7 @@ exports[`t-if can use some boolean operators in expressions 1`] = `
exports[`t-if div containing a t-if with two inner nodes 1`] = ` exports[`t-if div containing a t-if with two inner nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span>yip</span>\`); let block3 = createBlock(\`<span>yip</span>\`);
@@ -208,8 +199,7 @@ exports[`t-if div containing a t-if with two inner nodes 1`] = `
exports[`t-if dynamic content after t-if with two children nodes 1`] = ` exports[`t-if dynamic content after t-if with two children nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-text-0/></div>\`);
let block3 = createBlock(\`<p>1</p>\`); let block3 = createBlock(\`<p>1</p>\`);
@@ -222,8 +212,8 @@ exports[`t-if dynamic content after t-if with two children nodes 1`] = `
let b4 = block4(); let b4 = block4();
b2 = multi([b3, b4]); b2 = multi([b3, b4]);
} }
let d1 = ctx['text']; let txt1 = ctx['text'];
return block1([d1], [b2]); return block1([txt1], [b2]);
} }
}" }"
`; `;
@@ -231,8 +221,7 @@ exports[`t-if dynamic content after t-if with two children nodes 1`] = `
exports[`t-if just a t-if 1`] = ` exports[`t-if just a t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2; let b2;
@@ -247,8 +236,7 @@ exports[`t-if just a t-if 1`] = `
exports[`t-if simple t-if/t-else 1`] = ` exports[`t-if simple t-if/t-else 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3; let b2,b3;
@@ -265,8 +253,7 @@ exports[`t-if simple t-if/t-else 1`] = `
exports[`t-if simple t-if/t-else in a div 1`] = ` exports[`t-if simple t-if/t-else in a div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
@@ -285,8 +272,7 @@ exports[`t-if simple t-if/t-else in a div 1`] = `
exports[`t-if t-esc with t-elif 1`] = ` exports[`t-if t-esc with t-elif 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
@@ -305,8 +291,7 @@ exports[`t-if t-esc with t-elif 1`] = `
exports[`t-if t-esc with t-if 1`] = ` exports[`t-if t-esc with t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -323,8 +308,7 @@ exports[`t-if t-esc with t-if 1`] = `
exports[`t-if t-if and t-else with two nodes 1`] = ` exports[`t-if t-if and t-else with two nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block4 = createBlock(\`<span>a</span>\`); let block4 = createBlock(\`<span>a</span>\`);
let block5 = createBlock(\`<span>b</span>\`); let block5 = createBlock(\`<span>b</span>\`);
@@ -346,8 +330,7 @@ exports[`t-if t-if and t-else with two nodes 1`] = `
exports[`t-if t-if in a div 1`] = ` exports[`t-if t-if in a div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -364,8 +347,7 @@ exports[`t-if t-if in a div 1`] = `
exports[`t-if t-if in a t-if 1`] = ` exports[`t-if t-if in a t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span>1<block-child-0/></span>\`); let block2 = createBlock(\`<span>1<block-child-0/></span>\`);
@@ -384,11 +366,26 @@ exports[`t-if t-if in a t-if 1`] = `
}" }"
`; `;
exports[`t-if t-if with empty content 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2,b3;
b2 = text(\`hello\`);
if (ctx['condition']) {
b3 = text(\`\`);
}
return multi([b2, b3]);
}
}"
`;
exports[`t-if t-if/t-else with more content 1`] = ` exports[`t-if t-if/t-else with more content 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3; let b2,b3;
@@ -407,8 +404,8 @@ exports[`t-if t-if/t-else with more content 1`] = `
exports[`t-if t-set, then t-if 1`] = ` exports[`t-if t-set, then t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -428,8 +425,8 @@ exports[`t-if t-set, then t-if 1`] = `
exports[`t-if t-set, then t-if, part 2 1`] = ` exports[`t-if t-set, then t-if, part 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span>COUCOU</span>\`); let block2 = createBlock(\`<span>COUCOU</span>\`);
@@ -451,8 +448,8 @@ exports[`t-if t-set, then t-if, part 2 1`] = `
exports[`t-if t-set, then t-if, part 3 1`] = ` exports[`t-if t-set, then t-if, part 3 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
let block2 = createBlock(\`<span>AAA</span>\`); let block2 = createBlock(\`<span>AAA</span>\`);
@@ -477,8 +474,7 @@ exports[`t-if t-set, then t-if, part 3 1`] = `
exports[`t-if two consecutive t-if 1`] = ` exports[`t-if two consecutive t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3; let b2,b3;
@@ -496,8 +492,7 @@ exports[`t-if two consecutive t-if 1`] = `
exports[`t-if two consecutive t-if in a div 1`] = ` exports[`t-if two consecutive t-if in a div 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
@@ -517,8 +512,7 @@ exports[`t-if two consecutive t-if in a div 1`] = `
exports[`t-if two t-ifs next to each other 1`] = ` exports[`t-if two t-ifs next to each other 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
let block2 = createBlock(\`<span><block-text-0/></span>\`); let block2 = createBlock(\`<span><block-text-0/></span>\`);
@@ -528,8 +522,8 @@ exports[`t-if two t-ifs next to each other 1`] = `
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2,b3; let b2,b3;
if (ctx['condition']) { if (ctx['condition']) {
let d1 = ctx['text']; let txt1 = ctx['text'];
b2 = block2([d1]); b2 = block2([txt1]);
} }
if (ctx['condition']) { if (ctx['condition']) {
let b4 = block4(); let b4 = block4();
+48 -16
View File
@@ -3,15 +3,14 @@
exports[`t-key can use t-key directive on a node 1`] = ` exports[`t-key can use t-key directive on a node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['beer'].id; const tKey_1 = ctx['beer'].id;
let d1 = ctx['beer'].name; let txt1 = ctx['beer'].name;
return toggler(tKey_1, block1([d1])); return toggler(tKey_1, block1([txt1]));
} }
}" }"
`; `;
@@ -19,15 +18,14 @@ exports[`t-key can use t-key directive on a node 1`] = `
exports[`t-key can use t-key directive on a node 2 1`] = ` exports[`t-key can use t-key directive on a node 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['beer'].id; const tKey_1 = ctx['beer'].id;
let d1 = ctx['beer'].name; let txt1 = ctx['beer'].name;
return toggler(tKey_1, block1([d1])); return toggler(tKey_1, block1([txt1]));
} }
}" }"
`; `;
@@ -35,15 +33,14 @@ exports[`t-key can use t-key directive on a node 2 1`] = `
exports[`t-key can use t-key directive on a node as a function 1`] = ` exports[`t-key can use t-key directive on a node as a function 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['getKey'](ctx['beer']); const tKey_1 = ctx['getKey'](ctx['beer']);
let d1 = ctx['beer'].name; let txt1 = ctx['beer'].name;
return toggler(tKey_1, block1([d1])); return toggler(tKey_1, block1([txt1]));
} }
}" }"
`; `;
@@ -51,8 +48,8 @@ exports[`t-key can use t-key directive on a node as a function 1`] = `
exports[`t-key t-key directive in a list 1`] = ` exports[`t-key t-key directive in a list 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<ul><block-child-0/></ul>\`); let block1 = createBlock(\`<ul><block-child-0/></ul>\`);
let block3 = createBlock(\`<li><block-text-0/></li>\`); let block3 = createBlock(\`<li><block-text-0/></li>\`);
@@ -63,11 +60,46 @@ exports[`t-key t-key directive in a list 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`beer\`] = v_block2[i1]; ctx[\`beer\`] = v_block2[i1];
let key1 = ctx['beer'].id; let key1 = ctx['beer'].id;
let d1 = ctx['beer'].name; let txt1 = ctx['beer'].name;
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([txt1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
`; `;
exports[`t-key t-key on sub dom node pushes a child block in its parent 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
let block2 = createBlock(\`<span/>\`);
let block3 = createBlock(\`<div><h1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['hasSpan']) {
b2 = block2();
}
const tKey_1 = ctx['key'];
b3 = toggler(tKey_1, block3());
return block1([], [b2, b3]);
}
}"
`;
exports[`t-key t-key on sub dom node pushes a child block in its parent 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><h1/></div>\`);
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['key'];
return toggler(tKey_1, block1());
}
}"
`;
+132 -96
View File
@@ -3,8 +3,8 @@
exports[`t-out literal 1`] = ` exports[`t-out literal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -18,8 +18,8 @@ exports[`t-out literal 1`] = `
exports[`t-out literal, no outside html element 1`] = ` exports[`t-out literal, no outside html element 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return safeOutput('ok'); return safeOutput('ok');
@@ -30,8 +30,29 @@ exports[`t-out literal, no outside html element 1`] = `
exports[`t-out multiple calls to t-out 1`] = ` exports[`t-out multiple calls to t-out 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, zero, getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span>coucou</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
let b2 = block2();
ctx[zero] = b2;
let b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
}
}"
`;
exports[`t-out multiple calls to t-out 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { zero } = helpers;
let block1 = createBlock(\`<div><block-child-0/><div>Greeter</div><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><div>Greeter</div><block-child-1/></div>\`);
@@ -43,32 +64,11 @@ exports[`t-out multiple calls to t-out 1`] = `
}" }"
`; `;
exports[`t-out multiple calls to t-out 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span>coucou</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
let b2 = block2();
ctx[zero] = b2;
let b3 = callTemplate_2.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
}
}"
`;
exports[`t-out not escaping 1`] = ` exports[`t-out not escaping 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -82,8 +82,29 @@ exports[`t-out not escaping 1`] = `
exports[`t-out t-out 0 1`] = ` exports[`t-out t-out 0 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, zero, getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`_basic-callee\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>zero</div>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
let b2 = block2();
ctx[zero] = b2;
let b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
}
}"
`;
exports[`t-out t-out 0 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { zero } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -94,32 +115,11 @@ exports[`t-out t-out 0 1`] = `
}" }"
`; `;
exports[`t-out t-out 0 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`_basic-callee\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>zero</div>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
let b2 = block2();
ctx[zero] = b2;
let b3 = callTemplate_2.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
}
}"
`;
exports[`t-out t-out and another sibling node 1`] = ` exports[`t-out t-out and another sibling node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><span>hello</span><block-child-0/></span>\`); let block1 = createBlock(\`<span><span>hello</span><block-child-0/></span>\`);
@@ -133,17 +133,20 @@ exports[`t-out t-out and another sibling node 1`] = `
exports[`t-out t-out bdom 1`] = ` exports[`t-out t-out bdom 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, LazyValue, safeOutput } = helpers;
let block1 = createBlock(\`<div><span><block-child-0/></span></div>\`); let block1 = createBlock(\`<div><span><block-child-0/></span></div>\`);
let block2 = createBlock(\`<ol>set</ol>\`); let block2 = createBlock(\`<ol>set</ol>\`);
function value1(ctx, node, key = \\"\\") {
return block2();
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b2 = block2(); ctx[\`var\`] = new LazyValue(value1, ctx, node);
ctx[\`var\`] = b2;
let b3 = safeOutput(ctx['var']); let b3 = safeOutput(ctx['var']);
return block1([], [b3]); return block1([], [b3]);
} }
@@ -153,8 +156,8 @@ exports[`t-out t-out bdom 1`] = `
exports[`t-out t-out block 1`] = ` exports[`t-out t-out block 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -168,8 +171,8 @@ exports[`t-out t-out block 1`] = `
exports[`t-out t-out escaped 1`] = ` exports[`t-out t-out escaped 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -183,8 +186,8 @@ exports[`t-out t-out escaped 1`] = `
exports[`t-out t-out markedup 1`] = ` exports[`t-out t-out markedup 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -198,8 +201,8 @@ exports[`t-out t-out markedup 1`] = `
exports[`t-out t-out on a node with a body, as a default 1`] = ` exports[`t-out t-out on a node with a body, as a default 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput, withDefault } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -214,8 +217,8 @@ exports[`t-out t-out on a node with a body, as a default 1`] = `
exports[`t-out t-out on a node with a dom node in body, as a default 1`] = ` exports[`t-out t-out on a node with a dom node in body, as a default 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput, withDefault } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
let block3 = createBlock(\`<div>nope</div>\`); let block3 = createBlock(\`<div>nope</div>\`);
@@ -231,8 +234,8 @@ exports[`t-out t-out on a node with a dom node in body, as a default 1`] = `
exports[`t-out t-out switch escaped 1`] = ` exports[`t-out t-out switch escaped 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -246,8 +249,8 @@ exports[`t-out t-out switch escaped 1`] = `
exports[`t-out t-out switch escaped on markup 1`] = ` exports[`t-out t-out switch escaped on markup 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -261,8 +264,8 @@ exports[`t-out t-out switch escaped on markup 1`] = `
exports[`t-out t-out switch markup 1`] = ` exports[`t-out t-out switch markup 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -276,20 +279,23 @@ exports[`t-out t-out switch markup 1`] = `
exports[`t-out t-out switch markup on bdom 1`] = ` exports[`t-out t-out switch markup on bdom 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, LazyValue, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-1/></div>\`);
let block2 = createBlock(\`<ol>set</ol>\`); let block2 = createBlock(\`<ol>set</ol>\`);
let block3 = createBlock(\`<span><block-child-0/></span>\`); let block3 = createBlock(\`<span><block-child-0/></span>\`);
let block5 = createBlock(\`<span><block-child-0/></span>\`); let block5 = createBlock(\`<span><block-child-0/></span>\`);
function value1(ctx, node, key = \\"\\") {
return block2();
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b3,b5; let b3,b5;
let b2 = block2(); ctx[\`bdom\`] = new LazyValue(value1, ctx, node);
ctx[\`bdom\`] = b2;
if (ctx['hasBdom']) { if (ctx['hasBdom']) {
let b4 = safeOutput(ctx['bdom']); let b4 = safeOutput(ctx['bdom']);
b3 = block3([], [b4]); b3 = block3([], [b4]);
@@ -305,8 +311,8 @@ exports[`t-out t-out switch markup on bdom 1`] = `
exports[`t-out t-out switch markup on escaped 1`] = ` exports[`t-out t-out switch markup on escaped 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -320,8 +326,8 @@ exports[`t-out t-out switch markup on escaped 1`] = `
exports[`t-out t-out with a <t/> in body 1`] = ` exports[`t-out t-out with a <t/> in body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return safeOutput(ctx['var']); return safeOutput(ctx['var']);
@@ -332,8 +338,8 @@ exports[`t-out t-out with a <t/> in body 1`] = `
exports[`t-out t-out with arbitrary object 1`] = ` exports[`t-out t-out with arbitrary object 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -347,8 +353,8 @@ exports[`t-out t-out with arbitrary object 1`] = `
exports[`t-out t-out with arbitrary object 2 1`] = ` exports[`t-out t-out with arbitrary object 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -362,8 +368,8 @@ exports[`t-out t-out with arbitrary object 2 1`] = `
exports[`t-out t-out with comment 1`] = ` exports[`t-out t-out with comment 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -377,8 +383,8 @@ exports[`t-out t-out with comment 1`] = `
exports[`t-out t-out with just a t-set t-value in body 1`] = ` exports[`t-out t-out with just a t-set t-value in body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return safeOutput(ctx['var']); return safeOutput(ctx['var']);
@@ -389,8 +395,38 @@ exports[`t-out t-out with just a t-set t-value in body 1`] = `
exports[`t-out variable 1`] = ` exports[`t-out variable 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = safeOutput(ctx['var']);
return block1([], [b2]);
}
}"
`;
exports[`t-out with a String class 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = safeOutput(ctx['var']);
return block1([], [b2]);
}
}"
`;
exports[`t-out with an extended String class 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { safeOutput } = helpers;
let block1 = createBlock(\`<span><block-child-0/></span>\`); let block1 = createBlock(\`<span><block-child-0/></span>\`);
@@ -404,8 +440,8 @@ exports[`t-out variable 1`] = `
exports[`t-raw is deprecated should warn 1`] = ` exports[`t-raw is deprecated should warn 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -419,8 +455,8 @@ exports[`t-raw is deprecated should warn 1`] = `
exports[`t-raw is deprecated t-out is actually called in t-raw's place 1`] = ` exports[`t-raw is deprecated t-out is actually called in t-raw's place 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
+27 -32
View File
@@ -3,16 +3,15 @@
exports[`t-ref can get a dynamic ref on a node 1`] = ` exports[`t-ref can get a dynamic ref on a node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`); let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
const v1 = ctx['id']; const v1 = ctx['id'];
let d1 = (el) => refs[\`myspan\${v1}\`] = el; let ref1 = (el) => refs[\`myspan\${v1}\`] = el;
return block1([d1]); return block1([ref1]);
} }
}" }"
`; `;
@@ -20,15 +19,14 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
exports[`t-ref can get a ref on a node 1`] = ` exports[`t-ref can get a ref on a node 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`); let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
let d1 = (el) => refs[\`myspan\`] = el; const ref1 = (el) => refs[\`myspan\`] = el;
return block1([d1]); return block1([ref1]);
} }
}" }"
`; `;
@@ -36,14 +34,14 @@ exports[`t-ref can get a ref on a node 1`] = `
exports[`t-ref ref in a t-call 1`] = ` exports[`t-ref ref in a t-call 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_2 = getTemplate(\`sub\`); const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = callTemplate_2.call(this, ctx, node, key + \`__1\`); let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]); return block1([], [b2]);
} }
}" }"
@@ -52,15 +50,14 @@ exports[`t-ref ref in a t-call 1`] = `
exports[`t-ref ref in a t-call 2`] = ` exports[`t-ref ref in a t-call 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>1<span block-ref=\\"0\\"/>2</div>\`); let block1 = createBlock(\`<div>1<span block-ref=\\"0\\"/>2</div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
let d1 = (el) => refs[\`name\`] = el; const ref1 = (el) => refs[\`name\`] = el;
return block1([d1]); return block1([ref1]);
} }
}" }"
`; `;
@@ -68,18 +65,17 @@ exports[`t-ref ref in a t-call 2`] = `
exports[`t-ref ref in a t-if 1`] = ` exports[`t-ref ref in a t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span block-ref=\\"0\\"/>\`); let block2 = createBlock(\`<span block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
const ref1 = (el) => refs[\`name\`] = el;
let b2; let b2;
if (ctx['condition']) { if (ctx['condition']) {
let d1 = (el) => refs[\`name\`] = el; b2 = block2([ref1]);
b2 = block2([d1]);
} }
return block1([], [b2]); return block1([], [b2]);
} }
@@ -89,8 +85,8 @@ exports[`t-ref ref in a t-if 1`] = `
exports[`t-ref refs in a loop 1`] = ` exports[`t-ref refs in a loop 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div block-ref=\\"0\\"><block-text-1/></div>\`); let block3 = createBlock(\`<div block-ref=\\"0\\"><block-text-1/></div>\`);
@@ -103,10 +99,10 @@ exports[`t-ref refs in a loop 1`] = `
ctx[\`item\`] = v_block2[i1]; ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item']; let key1 = ctx['item'];
const tKey_1 = ctx['item']; const tKey_1 = ctx['item'];
const v2 = ctx['item']; const v1 = ctx['item'];
let d1 = (el) => refs[\`\${v2}\`] = el; let ref1 = (el) => refs[\`\${v1}\`] = el;
let d2 = ctx['item']; let txt1 = ctx['item'];
c_block2[i1] = withKey(block3([d1, d2]), tKey_1 + key1); c_block2[i1] = withKey(block3([ref1, txt1]), tKey_1 + key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
@@ -117,21 +113,20 @@ exports[`t-ref refs in a loop 1`] = `
exports[`t-ref two refs, one in a t-if 1`] = ` exports[`t-ref two refs, one in a t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/><p block-ref=\\"0\\"/></div>\`); let block1 = createBlock(\`<div><block-child-0/><p block-ref=\\"0\\"/></div>\`);
let block2 = createBlock(\`<span block-ref=\\"0\\"/>\`); let block2 = createBlock(\`<span block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs; const refs = ctx.__owl__.refs;
const ref1 = (el) => refs[\`name\`] = el;
const ref2 = (el) => refs[\`p\`] = el;
let b2; let b2;
if (ctx['condition']) { if (ctx['condition']) {
let d1 = (el) => refs[\`name\`] = el; b2 = block2([ref1]);
b2 = block2([d1]);
} }
let d2 = (el) => refs[\`p\`] = el; return block1([ref2], [b2]);
return block1([d2], [b2]);
} }
}" }"
`; `;
+191 -148
View File
@@ -3,8 +3,8 @@
exports[`t-set evaluate value expression 1`] = ` exports[`t-set evaluate value expression 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -12,8 +12,8 @@ exports[`t-set evaluate value expression 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"value\\", 1+2); setContextValue(ctx, \\"value\\", 1+2);
let d1 = ctx['value']; let txt1 = ctx['value'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -21,8 +21,8 @@ exports[`t-set evaluate value expression 1`] = `
exports[`t-set evaluate value expression, part 2 1`] = ` exports[`t-set evaluate value expression, part 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -30,8 +30,8 @@ exports[`t-set evaluate value expression, part 2 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"value\\", ctx['somevariable']+2); setContextValue(ctx, \\"value\\", ctx['somevariable']+2);
let d1 = ctx['value']; let txt1 = ctx['value'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -39,8 +39,8 @@ exports[`t-set evaluate value expression, part 2 1`] = `
exports[`t-set set from attribute literal (no outside div) 1`] = ` exports[`t-set set from attribute literal (no outside div) 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -54,8 +54,8 @@ exports[`t-set set from attribute literal (no outside div) 1`] = `
exports[`t-set set from attribute literal 1`] = ` exports[`t-set set from attribute literal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -63,8 +63,8 @@ exports[`t-set set from attribute literal 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"value\\", 'ok'); setContextValue(ctx, \\"value\\", 'ok');
let d1 = ctx['value']; let txt1 = ctx['value'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -72,8 +72,8 @@ exports[`t-set set from attribute literal 1`] = `
exports[`t-set set from attribute lookup 1`] = ` exports[`t-set set from attribute lookup 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -81,8 +81,33 @@ exports[`t-set set from attribute lookup 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"stuff\\", ctx['value']); setContextValue(ctx, \\"stuff\\", ctx['value']);
let d1 = ctx['stuff']; let txt1 = ctx['stuff'];
return block1([d1]); return block1([txt1]);
}
}"
`;
exports[`t-set set from body literal (with t-if/t-else 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { isBoundary, withDefault, LazyValue } = helpers;
function value1(ctx, node, key = \\"\\") {
let b2,b3;
if (ctx['condition']) {
b2 = text(\`true\`);
} else {
b3 = text(\`false\`);
}
return multi([b2, b3]);
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
ctx[\`value\`] = new LazyValue(value1, ctx, node);
return text(ctx['value']);
} }
}" }"
`; `;
@@ -90,8 +115,8 @@ exports[`t-set set from attribute lookup 1`] = `
exports[`t-set set from body literal 1`] = ` exports[`t-set set from body literal 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -105,18 +130,21 @@ exports[`t-set set from body literal 1`] = `
exports[`t-set set from body lookup 1`] = ` exports[`t-set set from body lookup 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, LazyValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
function value1(ctx, node, key = \\"\\") {
return text(ctx['value']);
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b2 = text(ctx['value']); ctx[\`stuff\`] = new LazyValue(value1, ctx, node);
ctx[\`stuff\`] = b2; let txt1 = ctx['stuff'];
let d1 = ctx['stuff']; return block1([txt1]);
return block1([d1]);
} }
}" }"
`; `;
@@ -124,8 +152,8 @@ exports[`t-set set from body lookup 1`] = `
exports[`t-set set from empty body 1`] = ` exports[`t-set set from empty body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -133,8 +161,8 @@ exports[`t-set set from empty body 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"stuff\\", null); setContextValue(ctx, \\"stuff\\", null);
let d1 = ctx['stuff']; let txt1 = ctx['stuff'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -142,8 +170,8 @@ exports[`t-set set from empty body 1`] = `
exports[`t-set t-set and t-if 1`] = ` exports[`t-set t-set and t-if 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
@@ -163,19 +191,22 @@ exports[`t-set t-set and t-if 1`] = `
exports[`t-set t-set body is evaluated immediately 1`] = ` exports[`t-set t-set body is evaluated immediately 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, LazyValue, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span><block-text-0/></span>\`); let block2 = createBlock(\`<span><block-text-0/></span>\`);
function value1(ctx, node, key = \\"\\") {
let txt1 = ctx['v1'];
return block2([txt1]);
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
let d1 = ctx['v1']; ctx[\`v2\`] = new LazyValue(value1, ctx, node);
let b2 = block2([d1]);
ctx[\`v2\`] = b2;
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
let b3 = safeOutput(ctx['v2']); let b3 = safeOutput(ctx['v2']);
return block1([], [b3]); return block1([], [b3]);
@@ -186,18 +217,20 @@ exports[`t-set t-set body is evaluated immediately 1`] = `
exports[`t-set t-set can't alter from within callee 1`] = ` exports[`t-set t-set can't alter from within callee 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`); let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let d1 = ctx['iter']; setContextValue(ctx, \\"iter\\", 'source');
setContextValue(ctx, \\"iter\\", 'called'); let txt1 = ctx['iter'];
let d2 = ctx['iter']; let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([d1, d2]); let txt2 = ctx['iter'];
return block1([txt1, txt2], [b2]);
} }
}" }"
`; `;
@@ -205,20 +238,18 @@ exports[`t-set t-set can't alter from within callee 1`] = `
exports[`t-set t-set can't alter from within callee 2`] = ` exports[`t-set t-set can't alter from within callee 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`); let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"iter\\", 'source'); let txt1 = ctx['iter'];
let d1 = ctx['iter']; setContextValue(ctx, \\"iter\\", 'called');
let b2 = callTemplate_2.call(this, ctx, node, key + \`__1\`); let txt2 = ctx['iter'];
let d2 = ctx['iter']; return block1([txt1, txt2]);
return block1([d1, d2], [b2]);
} }
}" }"
`; `;
@@ -226,28 +257,9 @@ exports[`t-set t-set can't alter from within callee 2`] = `
exports[`t-set t-set can't alter in t-call body 1`] = ` exports[`t-set t-set can't alter in t-call body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
let d1 = ctx['iter'];
setContextValue(ctx, \\"iter\\", 'called');
let d2 = ctx['iter'];
return block1([d1, d2]);
}
}"
`;
exports[`t-set t-set can't alter in t-call body 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_2 = getTemplate(\`sub\`);
let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`); let block1 = createBlock(\`<div><p><block-text-0/></p><block-child-0/><p><block-text-1/></p></div>\`);
@@ -255,14 +267,33 @@ exports[`t-set t-set can't alter in t-call body 2`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"iter\\", 'source'); setContextValue(ctx, \\"iter\\", 'source');
let d1 = ctx['iter']; let txt1 = ctx['iter'];
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1; ctx[isBoundary] = 1;
setContextValue(ctx, \\"iter\\", 'inCall'); setContextValue(ctx, \\"iter\\", 'inCall');
let b2 = callTemplate_2.call(this, ctx, node, key + \`__1\`); let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
ctx = ctx.__proto__; ctx = ctx.__proto__;
let d2 = ctx['iter']; let txt2 = ctx['iter'];
return block1([d1, d2], [b2]); return block1([txt1, txt2], [b2]);
}
}"
`;
exports[`t-set t-set can't alter in t-call body 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
let txt1 = ctx['iter'];
setContextValue(ctx, \\"iter\\", 'called');
let txt2 = ctx['iter'];
return block1([txt1, txt2]);
} }
}" }"
`; `;
@@ -270,8 +301,8 @@ exports[`t-set t-set can't alter in t-call body 2`] = `
exports[`t-set t-set does not modify render context existing key values 1`] = ` exports[`t-set t-set does not modify render context existing key values 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -279,8 +310,8 @@ exports[`t-set t-set does not modify render context existing key values 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"value\\", 35); setContextValue(ctx, \\"value\\", 35);
let d1 = ctx['value']; let txt1 = ctx['value'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -288,8 +319,8 @@ exports[`t-set t-set does not modify render context existing key values 1`] = `
exports[`t-set t-set evaluates an expression only once 1`] = ` exports[`t-set t-set evaluates an expression only once 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`); let block1 = createBlock(\`<div><block-text-0/><block-text-1/></div>\`);
@@ -297,9 +328,9 @@ exports[`t-set t-set evaluates an expression only once 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v\\", ctx['value']+' artois'); setContextValue(ctx, \\"v\\", ctx['value']+' artois');
let d1 = ctx['v']; let txt1 = ctx['v'];
let d2 = ctx['v']; let txt2 = ctx['v'];
return block1([d1, d2]); return block1([txt1, txt2]);
} }
}" }"
`; `;
@@ -307,8 +338,8 @@ exports[`t-set t-set evaluates an expression only once 1`] = `
exports[`t-set t-set outside modified in t-foreach 1`] = ` exports[`t-set t-set outside modified in t-foreach 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`); let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`);
let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`); let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`);
@@ -322,14 +353,14 @@ exports[`t-set t-set outside modified in t-foreach 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`val\`] = v_block2[i1]; ctx[\`val\`] = v_block2[i1];
let key1 = ctx['val']; let key1 = ctx['val'];
let d1 = ctx['iter']; let txt1 = ctx['iter'];
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([txt1]), key1);
setContextValue(ctx, \\"iter\\", ctx['iter']+1); setContextValue(ctx, \\"iter\\", ctx['iter']+1);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let d2 = ctx['iter']; let txt2 = ctx['iter'];
return block1([d2], [b2]); return block1([txt2], [b2]);
} }
}" }"
`; `;
@@ -337,8 +368,8 @@ exports[`t-set t-set outside modified in t-foreach 1`] = `
exports[`t-set t-set outside modified in t-foreach increment-after operator 1`] = ` exports[`t-set t-set outside modified in t-foreach increment-after operator 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`); let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`);
let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`); let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`);
@@ -352,14 +383,14 @@ exports[`t-set t-set outside modified in t-foreach increment-after operator 1`]
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`val\`] = v_block2[i1]; ctx[\`val\`] = v_block2[i1];
let key1 = ctx['val']; let key1 = ctx['val'];
let d1 = ctx['iter']; let txt1 = ctx['iter'];
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([txt1]), key1);
setContextValue(ctx, \\"iter\\", ctx['iter']++); setContextValue(ctx, \\"iter\\", ctx['iter']++);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let d2 = ctx['iter']; let txt2 = ctx['iter'];
return block1([d2], [b2]); return block1([txt2], [b2]);
} }
}" }"
`; `;
@@ -367,8 +398,8 @@ exports[`t-set t-set outside modified in t-foreach increment-after operator 1`]
exports[`t-set t-set outside modified in t-foreach increment-before operator 1`] = ` exports[`t-set t-set outside modified in t-foreach increment-before operator 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`); let block1 = createBlock(\`<div><block-child-0/><p>EndLoop: <block-text-0/></p></div>\`);
let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`); let block3 = createBlock(\`<p>InLoop: <block-text-0/></p>\`);
@@ -382,14 +413,14 @@ exports[`t-set t-set outside modified in t-foreach increment-before operator 1`]
for (let i1 = 0; i1 < l_block2; i1++) { for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`val\`] = v_block2[i1]; ctx[\`val\`] = v_block2[i1];
let key1 = ctx['val']; let key1 = ctx['val'];
let d1 = ctx['iter']; let txt1 = ctx['iter'];
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([txt1]), key1);
setContextValue(ctx, \\"iter\\", ++ctx['iter']); setContextValue(ctx, \\"iter\\", ++ctx['iter']);
} }
ctx = ctx.__proto__; ctx = ctx.__proto__;
let b2 = list(c_block2); let b2 = list(c_block2);
let d2 = ctx['iter']; let txt2 = ctx['iter'];
return block1([d2], [b2]); return block1([txt2], [b2]);
} }
}" }"
`; `;
@@ -397,8 +428,8 @@ exports[`t-set t-set outside modified in t-foreach increment-before operator 1`]
exports[`t-set t-set should reuse variable if possible 1`] = ` exports[`t-set t-set should reuse variable if possible 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div><span>v<block-text-0/></span></div>\`); let block3 = createBlock(\`<div><span>v<block-text-0/></span></div>\`);
@@ -413,9 +444,9 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
ctx[\`elem\`] = v_block2[i1]; ctx[\`elem\`] = v_block2[i1];
ctx[\`elem_index\`] = i1; ctx[\`elem_index\`] = i1;
let key1 = ctx['elem_index']; let key1 = ctx['elem_index'];
let d1 = ctx['v']; let txt1 = ctx['v'];
setContextValue(ctx, \\"v\\", ctx['elem']); setContextValue(ctx, \\"v\\", ctx['elem']);
c_block2[i1] = withKey(block3([d1]), key1); c_block2[i1] = withKey(block3([txt1]), key1);
} }
let b2 = list(c_block2); let b2 = list(c_block2);
return block1([], [b2]); return block1([], [b2]);
@@ -426,20 +457,23 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
exports[`t-set t-set with content and sub t-esc 1`] = ` exports[`t-set t-set with content and sub t-esc 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, LazyValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
function value1(ctx, node, key = \\"\\") {
let b3 = text(ctx['beep']);
let b4 = text(\` boop\`);
return multi([b3, b4]);
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b3 = text(ctx['beep']); ctx[\`setvar\`] = new LazyValue(value1, ctx, node);
let b4 = text(\` boop\`); let txt1 = ctx['setvar'];
let b2 = multi([b3, b4]); return block1([txt1]);
ctx[\`setvar\`] = b2;
let d1 = ctx['setvar'];
return block1([d1]);
} }
}" }"
`; `;
@@ -447,20 +481,23 @@ exports[`t-set t-set with content and sub t-esc 1`] = `
exports[`t-set t-set with t-value (falsy) and body 1`] = ` exports[`t-set t-set with t-value (falsy) and body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, LazyValue, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span><block-text-0/></span>\`); let block2 = createBlock(\`<span><block-text-0/></span>\`);
function value1(ctx, node, key = \\"\\") {
let txt1 = ctx['v1'];
return block2([txt1]);
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", false); setContextValue(ctx, \\"v3\\", false);
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
let d1 = ctx['v1']; ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, node));
let b2 = block2([d1]);
ctx[\`v2\`] = withDefault(ctx['v3'], b2);
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", true); setContextValue(ctx, \\"v3\\", true);
let b3 = safeOutput(ctx['v2']); let b3 = safeOutput(ctx['v2']);
@@ -472,20 +509,23 @@ exports[`t-set t-set with t-value (falsy) and body 1`] = `
exports[`t-set t-set with t-value (truthy) and body 1`] = ` exports[`t-set t-set with t-value (truthy) and body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue, LazyValue, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span><block-text-0/></span>\`); let block2 = createBlock(\`<span><block-text-0/></span>\`);
function value1(ctx, node, key = \\"\\") {
let txt1 = ctx['v1'];
return block2([txt1]);
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"v3\\", 'Truthy'); setContextValue(ctx, \\"v3\\", 'Truthy');
setContextValue(ctx, \\"v1\\", 'before'); setContextValue(ctx, \\"v1\\", 'before');
let d1 = ctx['v1']; ctx[\`v2\`] = withDefault(ctx['v3'], new LazyValue(value1, ctx, node));
let b2 = block2([d1]);
ctx[\`v2\`] = withDefault(ctx['v3'], b2);
setContextValue(ctx, \\"v1\\", 'after'); setContextValue(ctx, \\"v1\\", 'after');
setContextValue(ctx, \\"v3\\", false); setContextValue(ctx, \\"v3\\", false);
let b3 = safeOutput(ctx['v2']); let b3 = safeOutput(ctx['v2']);
@@ -497,8 +537,8 @@ exports[`t-set t-set with t-value (truthy) and body 1`] = `
exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = ` exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-0/><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-0/><block-text-0/></div>\`);
@@ -510,8 +550,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = `
} else { } else {
setContextValue(ctx, \\"ourvar\\", 0); setContextValue(ctx, \\"ourvar\\", 0);
} }
let d1 = ctx['ourvar']; let txt1 = ctx['ourvar'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -519,8 +559,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = `
exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = ` exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/><block-child-0/><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-child-0/><block-child-0/><block-text-0/></div>\`);
@@ -532,8 +572,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = `
} else { } else {
setContextValue(ctx, \\"ourvar\\", \`0\`); setContextValue(ctx, \\"ourvar\\", \`0\`);
} }
let d1 = ctx['ourvar']; let txt1 = ctx['ourvar'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -541,8 +581,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = `
exports[`t-set t-set, t-if, and mix of expression/body lookup, 3 1`] = ` exports[`t-set t-set, t-if, and mix of expression/body lookup, 3 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
@@ -562,19 +602,22 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 3 1`] = `
exports[`t-set value priority (with non text body 1`] = ` exports[`t-set value priority (with non text body 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, LazyValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
let block2 = createBlock(\`<span>2</span>\`); let block2 = createBlock(\`<span>2</span>\`);
function value1(ctx, node, key = \\"\\") {
return block2();
}
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
let b2 = block2(); ctx[\`value\`] = withDefault(1, new LazyValue(value1, ctx, node));
ctx[\`value\`] = withDefault(1, b2); let txt1 = ctx['value'];
let d1 = ctx['value']; return block1([txt1]);
return block1([d1]);
} }
}" }"
`; `;
@@ -582,8 +625,8 @@ exports[`t-set value priority (with non text body 1`] = `
exports[`t-set value priority 1`] = ` exports[`t-set value priority 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { isBoundary, withDefault, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-text-0/></div>\`); let block1 = createBlock(\`<div><block-text-0/></div>\`);
@@ -591,8 +634,8 @@ exports[`t-set value priority 1`] = `
ctx = Object.create(ctx); ctx = Object.create(ctx);
ctx[isBoundary] = 1 ctx[isBoundary] = 1
setContextValue(ctx, \\"value\\", withDefault(1, \`2\`)); setContextValue(ctx, \\"value\\", withDefault(1, \`2\`));
let d1 = ctx['value']; let txt1 = ctx['value'];
return block1([d1]); return block1([txt1]);
} }
}" }"
`; `;
@@ -3,8 +3,7 @@
exports[`qweb t-tag can fallback if falsy tag 1`] = ` exports[`qweb t-tag can fallback if falsy tag 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 'fallback'}/>\`); let block1 = tag => createBlock(\`<\${tag || 'fallback'}/>\`);
@@ -18,8 +17,7 @@ exports[`qweb t-tag can fallback if falsy tag 1`] = `
exports[`qweb t-tag can update 1`] = ` exports[`qweb t-tag can update 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 't'}/>\`); let block1 = tag => createBlock(\`<\${tag || 't'}/>\`);
@@ -33,8 +31,7 @@ exports[`qweb t-tag can update 1`] = `
exports[`qweb t-tag simple usecases 1`] = ` exports[`qweb t-tag simple usecases 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 't'}/>\`); let block1 = tag => createBlock(\`<\${tag || 't'}/>\`);
@@ -48,8 +45,7 @@ exports[`qweb t-tag simple usecases 1`] = `
exports[`qweb t-tag simple usecases 2`] = ` exports[`qweb t-tag simple usecases 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 't'}>text</\${tag || 't'}>\`); let block1 = tag => createBlock(\`<\${tag || 't'}>text</\${tag || 't'}>\`);
@@ -63,8 +59,7 @@ exports[`qweb t-tag simple usecases 2`] = `
exports[`qweb t-tag with multiple attributes 1`] = ` exports[`qweb t-tag with multiple attributes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 't'} class=\\"blueberry\\" taste=\\"raspberry\\">gooseberry</\${tag || 't'}>\`); let block1 = tag => createBlock(\`<\${tag || 't'} class=\\"blueberry\\" taste=\\"raspberry\\">gooseberry</\${tag || 't'}>\`);
@@ -78,8 +73,7 @@ exports[`qweb t-tag with multiple attributes 1`] = `
exports[`qweb t-tag with multiple child nodes 1`] = ` exports[`qweb t-tag with multiple child nodes 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 't'}> pear <span>apple</span> strawberry </\${tag || 't'}>\`); let block1 = tag => createBlock(\`<\${tag || 't'}> pear <span>apple</span> strawberry </\${tag || 't'}>\`);
@@ -93,8 +87,7 @@ exports[`qweb t-tag with multiple child nodes 1`] = `
exports[`qweb t-tag with multiple t-tag in same template 1`] = ` exports[`qweb t-tag with multiple t-tag in same template 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = tag => createBlock(\`<\${tag || 't'}><block-child-0/></\${tag || 't'}>\`); let block1 = tag => createBlock(\`<\${tag || 't'}><block-child-0/></\${tag || 't'}>\`);
let block2 = tag => createBlock(\`<\${tag || 't'}>baz</\${tag || 't'}>\`); let block2 = tag => createBlock(\`<\${tag || 't'}>baz</\${tag || 't'}>\`);
@@ -111,8 +104,7 @@ exports[`qweb t-tag with multiple t-tag in same template 1`] = `
exports[`qweb t-tag with multiple t-tag in same template, part 2 1`] = ` exports[`qweb t-tag with multiple t-tag in same template, part 2 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block2 = tag => createBlock(\`<\${tag || 't'}>bar</\${tag || 't'}>\`); let block2 = tag => createBlock(\`<\${tag || 't'}>bar</\${tag || 't'}>\`);
let block3 = tag => createBlock(\`<\${tag || 't'}>baz</\${tag || 't'}>\`); let block3 = tag => createBlock(\`<\${tag || 't'}>baz</\${tag || 't'}>\`);
@@ -1,10 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loading templates addTemplates does not modify its xml document in place 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['value'];
return block1([txt1]);
}
}"
`;
exports[`loading templates can initialize qweb with a string 1`] = ` exports[`loading templates can initialize qweb with a string 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>jupiler</div>\`); let block1 = createBlock(\`<div>jupiler</div>\`);
@@ -17,8 +30,7 @@ exports[`loading templates can initialize qweb with a string 1`] = `
exports[`loading templates can initialize qweb with an XMLDocument 1`] = ` exports[`loading templates can initialize qweb with an XMLDocument 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div>jupiler</div>\`); let block1 = createBlock(\`<div>jupiler</div>\`);
@@ -31,16 +43,15 @@ exports[`loading templates can initialize qweb with an XMLDocument 1`] = `
exports[`loading templates can load a few templates from a xml string 1`] = ` exports[`loading templates can load a few templates from a xml string 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`items\`);
let block2 = createBlock(\`<li>ok</li>\`); let block1 = createBlock(\`<ul><block-child-0/></ul>\`);
let block3 = createBlock(\`<li>foo</li>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let b2 = block2(); let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
let b3 = block3(); return block1([], [b2]);
return multi([b2, b3]);
} }
}" }"
`; `;
@@ -48,22 +59,7 @@ exports[`loading templates can load a few templates from a xml string 1`] = `
exports[`loading templates can load a few templates from a xml string 2`] = ` exports[`loading templates can load a few templates from a xml string 2`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<ul/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`loading templates can load a few templates from an XMLDocument 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block2 = createBlock(\`<li>ok</li>\`); let block2 = createBlock(\`<li>ok</li>\`);
let block3 = createBlock(\`<li>foo</li>\`); let block3 = createBlock(\`<li>foo</li>\`);
@@ -76,16 +72,34 @@ exports[`loading templates can load a few templates from an XMLDocument 1`] = `
}" }"
`; `;
exports[`loading templates can load a few templates from an XMLDocument 2`] = ` exports[`loading templates can load a few templates from an XMLDocument 1`] = `
"function anonymous(bdom, helpers "function anonymous(bdom, helpers
) { ) {
let { text, createBlock, list, multi, html, toggler, component } = bdom; let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers; let { getTemplate } = helpers;
const callTemplate_1 = getTemplate(\`items\`);
let block1 = createBlock(\`<ul/>\`); let block1 = createBlock(\`<ul><block-child-0/></ul>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
return block1(); let b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
exports[`loading templates can load a few templates from an XMLDocument 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block2 = createBlock(\`<li>ok</li>\`);
let block3 = createBlock(\`<li>foo</li>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = block2();
let b3 = block3();
return multi([b2, b3]);
} }
}" }"
`; `;

Some files were not shown because too many files have changed in this diff Show More