Compare commits

..

241 Commits

Author SHA1 Message Date
Samuel Degueldre aef6923c23 PROTOTYPE: batch renders in animation frame instead of microtick 2022-02-23 11:55:53 +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
28 changed files with 454 additions and 116 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ name: Node.js CI
on:
pull_request:
branches: [ master, owl-next ]
branches: [ master ]
jobs:
build:
+1 -1
View File
@@ -121,5 +121,5 @@ npm install @odoo/owl
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)
+1 -1
View File
@@ -352,7 +352,7 @@ cleaning operation, since the component may be destroyed before it has even been
mounted. The `willDestroy` hook is useful in that situation, since it is always
called.
The `onWillDestroy` hook is used to register a function that will be executed at
The `onWillUnmount` hook is used to register a function that will be executed at
this moment:
```javascript
-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 |
+7
View File
@@ -117,3 +117,10 @@ 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
```
+11
View File
@@ -21,6 +21,7 @@
- [Fragments](#fragments)
- [Inline templates](#inline-templates)
- [Rendering svg](#rendering-svg)
- [Restrictions](#restrictions)
## Overview
@@ -680,3 +681,13 @@ 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>
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.0-alpha.1",
"version": "2.0.0-alpha.2",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.4.7
- Current version: 1.4.10
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+7 -3
View File
@@ -18,10 +18,14 @@ export interface AppConfig<P, E> extends TemplateSetConfig {
test?: boolean;
}
export const DEV_MSG = `Owl is running in 'dev' mode.
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.
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 abstract new (...args: any) => any = any,
@@ -43,7 +47,7 @@ export class App<
this.dev = true;
}
if (this.dev && !config.test) {
console.info(DEV_MSG);
console.info(DEV_MSG());
}
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
+7 -2
View File
@@ -1,6 +1,6 @@
import { createBlock, html, list, multi, text, toggler, comment } from "../blockdom";
import { compile, Template } from "../compiler";
import { component } from "../component/component_node";
import { component, getCurrent } from "../component/component_node";
import { UTILS } from "./template_helpers";
const bdom = { text, createBlock, list, multi, html, toggler, component, comment };
@@ -94,7 +94,12 @@ export class TemplateSet {
if (!(name in this.templates)) {
const rawTemplate = this.rawTemplates[name];
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 = this._compileTemplate(name, rawTemplate);
// first add a function to lazily get the template, in case there is a
+1 -1
View File
@@ -139,7 +139,7 @@ export function updateClass(this: HTMLElement, val: any, oldVal: any) {
export function makePropSetter(name: string): Setter<HTMLElement> {
return function setProp(this: HTMLElement, value: any) {
(this as any)[name] = value;
(this as any)[name] = value || "";
};
}
+5
View File
@@ -305,6 +305,9 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (tagName === "t" && !dynamicTag) {
return null;
}
if (tagName.startsWith("block-")) {
throw new Error(`Invalid tag name: '${tagName}'`);
}
ctx = Object.assign({}, ctx);
if (tagName === "pre") {
ctx.inPreTag = true;
@@ -371,6 +374,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
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")) {
throw new Error(`Unknown QWeb directive: '${attr}'`);
+54 -35
View File
@@ -120,20 +120,30 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
this.initiateRender(fiber);
}
async initiateRender(fiber: Fiber | MountFiber) {
initiateRender(fiber: Fiber | MountFiber) {
this.fiber = fiber;
if (this.mounted.length) {
fiber.root!.mounted.push(fiber);
}
const component = this.component;
const finish = () => {
if (this.status === STATUS.NEW && this.fiber === fiber) {
this._render(fiber);
}
}
let willStartResults;
try {
await Promise.all(this.willStart.map((f) => f.call(component)));
willStartResults = this.willStart.map((f) => f.call(component));
} catch (e) {
handleError({ node: this, error: e });
return;
}
if (this.status === STATUS.NEW && this.fiber === fiber) {
this._render(fiber);
if (willStartResults.some(result => result instanceof Promise)) {
Promise.all(willStartResults)
.then(finish)
.catch(e => handleError({ node: this, error: e }));
} else {
finish();
}
}
@@ -153,25 +163,27 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
const fiber = makeRootFiber(this);
this.fiber = fiber;
debugger;
window.requestAnimationFrame(() => {
if (this.status === STATUS.DESTROYED) {
return;
}
// We only want to actually render the component if the following two
// conditions are true:
// * this.fiber: it could be null, in which case the render has been cancelled
// * (current || !fiber.parent): if current is not null, this means that the
// render function was called when a render was already occurring. In this
// case, the pending rendering was cancelled, and the fiber needs to be
// rendered to complete the work. If current is null, we check that the
// fiber has no parent. If that is the case, the fiber was downgraded from
// 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
// in the next microtick anyway, so we should not render it again.
if (this.fiber === fiber && (current || !fiber.parent)) {
this._render(fiber);
}
});
this.app.scheduler.addFiber(fiber);
await Promise.resolve();
if (this.status === STATUS.DESTROYED) {
return;
}
// We only want to actually render the component if the following two
// conditions are true:
// * this.fiber: it could be null, in which case the render has been cancelled
// * (current || !fiber.parent): if current is not null, this means that the
// render function was called when a render was already occurring. In this
// case, the pending rendering was cancelled, and the fiber needs to be
// rendered to complete the work. If current is null, we check that the
// fiber has no parent. If that is the case, the fiber was downgraded from
// 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
// in the next microtick anyway, so we should not render it again.
if (this.fiber === fiber && (current || !fiber.parent)) {
this._render(fiber);
}
}
_render(fiber: Fiber | RootFiber) {
@@ -207,25 +219,32 @@ export class ComponentNode<P = any, E = any> implements VNode<ComponentNode<P, E
this.status = STATUS.DESTROYED;
}
async updateAndRender(props: any, parentFiber: Fiber) {
updateAndRender(props: any, parentFiber: Fiber) {
// update
const fiber = makeChildFiber(this, parentFiber);
this.fiber = fiber;
const component = this.component;
applyDefaultProps(props, component.constructor as any);
const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
await prom;
if (fiber !== this.fiber) {
return;
const finish = () => {
if (fiber !== this.fiber) {
return;
}
component.props = props;
this._render(fiber);
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
}
}
component.props = props;
this._render(fiber);
const parentRoot = parentFiber.root!;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
}
if (this.patched.length) {
parentRoot.patched.push(fiber);
const willUpdatePropsResults = this.willUpdateProps.map((f) => f.call(component, props));
if (willUpdatePropsResults.some(res => res instanceof Promise)) {
Promise.all(willUpdatePropsResults).then(finish);
} else {
finish();
}
}
+4 -6
View File
@@ -6,8 +6,7 @@ import { STATUS } from "./status";
export function makeChildFiber(node: ComponentNode, parent: Fiber): Fiber {
let current = node.fiber;
if (current) {
let root = parent.root;
cancelFibers(root, current.children);
cancelFibers(current.children);
current.root = null;
}
return new Fiber(node, parent);
@@ -17,7 +16,7 @@ export function makeRootFiber(node: ComponentNode): Fiber {
let current = node.fiber;
if (current) {
let root = current.root!;
root.counter -= cancelFibers(root, current.children);
root.counter -= cancelFibers(current.children);
current.children = [];
root.counter++;
current.bdom = null;
@@ -42,15 +41,14 @@ export function makeRootFiber(node: ComponentNode): Fiber {
/**
* @returns number of not-yet rendered fibers cancelled
*/
function cancelFibers(root: any, fibers: Fiber[]): number {
function cancelFibers(fibers: Fiber[]): number {
let result = 0;
for (let fiber of fibers) {
fiber.node.fiber = null;
fiber.root = root;
if (!fiber.bdom) {
result++;
}
result += cancelFibers(root, fiber.children);
result += cancelFibers(fiber.children);
}
return result;
}
+46 -11
View File
@@ -1,62 +1,97 @@
import { getCurrent } from "./component_node";
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
// -----------------------------------------------------------------------------
export function onWillStart(fn: () => Promise<void> | void | any) {
const node = getCurrent();
node.willStart.push(fn.bind(node.component));
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) {
const node = getCurrent();
node.willUpdateProps.push(fn.bind(node.component));
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
}
export function onMounted(fn: () => void | any) {
const node = getCurrent();
node.mounted.push(fn.bind(node.component));
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) {
const node = getCurrent();
node.willPatch.unshift(fn.bind(node.component));
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
}
export function onPatched(fn: () => void | any) {
const node = getCurrent();
node.patched.push(fn.bind(node.component));
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) {
const node = getCurrent();
node.willUnmount.unshift(fn.bind(node.component));
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
}
export function onWillDestroy(fn: () => Promise<void> | void | any) {
const node = getCurrent();
node.willDestroy.push(fn.bind(node.component));
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
}
export function onWillRender(fn: () => void | any) {
const node = getCurrent();
const renderFn = node.renderFn;
node.renderFn = () => {
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.renderFn = decorate(() => {
fn.call(node.component);
return renderFn();
};
}, "onWillRender");
}
export function onRendered(fn: () => void | any) {
const node = getCurrent();
const renderFn = node.renderFn;
node.renderFn = () => {
const decorate = node.app.dev ? wrapError : (fn: any) => fn;
node.renderFn = decorate(() => {
const result = renderFn();
fn.call(node.component);
return result;
};
}, "onRendered");
}
type OnErrorCallback = (error: any) => void | any;
+1 -1
View File
@@ -57,7 +57,7 @@ export function markRaw<T extends Target>(value: T): NonReactive<T> {
* @returns the underlying value
*/
export function toRaw<T extends object>(value: Reactive<T>): T {
return value[TARGET];
return value[TARGET] || value;
}
const targetToKeysToCallbacks = new WeakMap<Target, Map<ObjectKey, Set<Callback>>>();
+9
View File
@@ -169,6 +169,15 @@ describe("properties", () => {
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", () => {
// render input with initial value
const block = createBlock(`<input type="checkbox" block-attribute-0="checked"/>`);
+13
View File
@@ -0,0 +1,13 @@
import { renderToString } from "../helpers";
describe("blacklisted tags and attributes", () => {
test("template with block-text tag", () => {
const template = `<div><block-text-0/>hello</div>`;
expect(() => renderToString(template)).toThrow("Invalid tag name: 'block-text-0'");
});
test("template with block-handler tag", () => {
const template = `<div block-handler-0="click">hello</div>`;
expect(() => renderToString(template)).toThrow("Invalid attribute: 'block-handler-0'");
});
});
@@ -1092,6 +1092,19 @@ exports[`errors and promises an error in mounted call will reject the mount prom
}"
`;
exports[`errors and promises an error in onMounted callback will have the component's setup in its stack trace 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div>abc</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`errors and promises an error in patched call will reject the render promise 1`] = `
"function anonymous(bdom, helpers
) {
@@ -410,6 +410,24 @@ exports[`t-model directive on an textarea 1`] = `
}"
`;
exports[`t-model directive t-model on an input with an undefined value 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<input block-attribute-0=\\"value\\" block-handler-1=\\"input\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([attr1, hdlr1]);
}
}"
`;
exports[`t-model directive t-model on select with static options 1`] = `
"function anonymous(bdom, helpers
) {
+1 -1
View File
@@ -204,7 +204,7 @@ describe("basics", () => {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe('Missing template: "wrongtemplate"');
expect(error!.message).toBe('Missing template: "wrongtemplate" (for component "Test")');
});
test("class component with dynamic text", async () => {
+2 -5
View File
@@ -97,17 +97,14 @@ test("destroying/recreating a subwidget with different props (if start is not ov
expect(["W:setup", "W:willStart", "W:willRender", "W:rendered", "W:mounted"]).toBeLogged();
expect(n).toBe(0);
w.state.val = 2;
await nextMicroTick();
await nextMicroTick();
await new Promise(requestAnimationFrame); // calls to _render are batched per animation frame
expect(n).toBe(1);
expect(["W:willRender", "Child:setup", "Child:willStart", "W:rendered"]).toBeLogged();
w.state.val = 3;
await nextMicroTick();
await nextMicroTick();
await new Promise(requestAnimationFrame);
expect(n).toBe(2);
expect([
+38 -17
View File
@@ -82,8 +82,6 @@ describe("basics", () => {
});
test("display a nice error if it cannot find component (in dev mode)", async () => {
const info = console.info;
console.info = jest.fn(() => {}); // dev mode message
class SomeComponent extends Component {}
class Parent extends Component {
static template = xml`<SomeMispelledComponent />`;
@@ -91,7 +89,7 @@ describe("basics", () => {
}
let error: Error;
try {
await mount(Parent, fixture, { dev: true });
await mount(Parent, fixture, { test: true });
} catch (e) {
error = e as Error;
}
@@ -100,8 +98,6 @@ describe("basics", () => {
expect(console.error).toBeCalledTimes(0);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
expect(console.info).toBeCalledTimes(1);
console.info = info;
});
test("simple catchError", async () => {
@@ -179,6 +175,30 @@ describe("errors and promises", () => {
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
class App extends Component {
static template = xml`<div>abc</div>`;
setup() {
onMounted(() => {
throw new Error("boom");
});
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.stack).toContain("App.setup");
expect(error!.stack).toContain("error_handling.test.ts");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("an error in willPatch call will reject the render promise", async () => {
class Root extends Component {
static template = xml`<div><t t-esc="val"/></div>`;
@@ -191,13 +211,13 @@ describe("errors and promises", () => {
}
}
const root = await mount(Root, fixture);
const root = await mount(Root, fixture, { test: true });
root.val = 4;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
expect(error!.message).toBe("boom");
expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
@@ -214,13 +234,13 @@ describe("errors and promises", () => {
}
}
const root = await mount(Root, fixture);
const root = await mount(Root, fixture, { test: true });
root.val = 4;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
expect(error!.message).toBe("boom");
expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
@@ -296,7 +316,6 @@ describe("errors and promises", () => {
});
test("errors in mounted and in willUnmount", async () => {
expect.assertions(4);
class Example extends Component {
static template = xml`<div/>`;
val: any;
@@ -313,9 +332,11 @@ describe("errors and promises", () => {
}
try {
await mount(Example, fixture);
await mount(Example, fixture, { test: true });
} catch (e) {
expect((e as Error).message).toBe("Error in mounted");
expect((e as Error).message).toBe(
`The following error occurred in onMounted: "Error in mounted"`
);
}
// 1 additional error is logged because the destruction of the app causes
// the onWillUnmount hook to be called and to fail
@@ -385,14 +406,14 @@ describe("can catch errors", () => {
});
}
}
let e: any = null;
let e: Error;
try {
await mount(Root, fixture);
await mount(Root, fixture, { test: true });
} catch (error) {
e = error;
e = error as Error;
}
expect(e.message).toBe(
"No active component (a hook function should only be called in 'setup')"
expect(e!.message).toBe(
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
);
});
+1 -1
View File
@@ -12,7 +12,7 @@ let mockConsoleWarn: any;
beforeAll(() => {
console.info = (message: any) => {
if (message === DEV_MSG) {
if (message === DEV_MSG()) {
return;
}
info(message);
+13
View File
@@ -30,6 +30,19 @@ describe("t-model directive", () => {
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
});
test("t-model on an input with an undefined value", async () => {
class SomeComponent extends Component {
static template = xml`<input t-model="state.text"/>`;
state = useState({ text: undefined });
}
await mount(SomeComponent, fixture);
expect(fixture.innerHTML).toBe("<input>");
const input = fixture.querySelector("input")!;
expect(input.value).toBe("");
});
test("basic use, on an input with bracket expression", async () => {
class SomeComponent extends Component {
static template = xml`
+1 -1
View File
@@ -29,7 +29,7 @@ snapshotEverything();
beforeAll(() => {
console.info = (message: any) => {
if (message === DEV_MSG) {
if (message === DEV_MSG()) {
return;
}
info(message);
+5
View File
@@ -1131,6 +1131,11 @@ describe("toRaw", () => {
expect(reactiveObj).not.toBe(obj);
expect(toRaw(reactiveObj as Reactive<typeof obj>)).toBe(obj);
});
test("giving a non reactive to toRaw return the object itself", () => {
const obj = { value: 1 };
expect(toRaw(obj as Reactive<typeof obj>)).toBe(obj);
});
});
describe("Reactivity: useState", () => {
+192 -2
View File
@@ -24,8 +24,8 @@ class Root extends Component {
}
}
// Application setup
mount(Root, document.body, { templates: TEMPLATES, dev: true });`;
// dev=false for benchmarking. we don't want to benchmark dev code!
mount(Root, document.body, { templates: TEMPLATES, dev: false });`;
const COMPONENTS_XML = /*xml*/`
<templates>
@@ -1573,6 +1573,191 @@ class Root extends Component {
mount(Root, document.body, { templates: TEMPLATES, dev: true});
`;
const BENCHMARK_JS = `
const { Component, mount, xml, useState, onWillRender, onPatched} = owl;
// -----------------------------------------------------------------------------
// Data generation
// -----------------------------------------------------------------------------
let idCounter = 1;
const adjectives = [
"pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain",
"quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd",
"unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"];
const colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"];
const nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"];
function _random (max) { return Math.round(Math.random() * 1000) % max; };
function buildData(count) {
const data = new Array(count);
for (let i = 0; i < count; i++) {
const label = \`\${adjectives[_random(adjectives.length)]} \${colours[_random(colours.length)]} \${nouns[_random(nouns.length)]}\`;
data[i] = {
id: idCounter++,
label,
};
}
return data;
}
// -----------------------------------------------------------------------------
// Components
// -----------------------------------------------------------------------------
class Button extends Component {
static template = xml\`
<div class='col-sm-6 smallpad'>
<button t-att-id="props.id" class='btn btn-primary btn-block' type='button' t-on-click="props.onClick">
<t t-esc="props.text"/>
</button>
</div>\`;
}
class Row extends Component {
static template = xml\`
<tr t-att-class="props.isSelected ? 'danger' : ''">
<td class="col-md-1" t-esc="props.row.id" />
<td class="col-md-4">
<a t-on-click="() => props.onSelect(props.row.id)" t-esc="props.row.label" />
</td>
<td class="col-md-1">
<a t-on-click="() => props.onRemove(props.row.id)" class="remove">[x]
<span class='glyphicon glyphicon-remove' aria-hidden="true" />
</a>
</td>
<td class='col-md-6'/>
</tr>\`
}
class Root extends Component {
static template = xml\`
<div class='container'>
<div class='jumbotron'>
<div class='row'>
<div class='col-md-6'>
<h1>Owl Keyed</h1>
</div>
<div class='col-md-6'>
<div class='row'>
<Button id="'run'" onClick.bind="run" text="'Create 1,000 rows'" />
<Button id="'runlots'" onClick.bind="runLots" text="'Create 10,000 rows'" />
<Button id="'add'" onClick.bind="add" text="'Append 1,000 rows'" />
<Button id="'update'" onClick.bind="update" text="'Update every 10th row'" />
<Button id="'clear'" onClick.bind="clear" text="'Clear'" />
<Button id="'swaprows'" onClick.bind="swapRows" text="'Swap Rows'" />
</div>
</div>
</div>
</div>
<table class='table table-hover table-striped test-data'>
<tbody>
<t t-foreach="state.rows" t-as="row" t-key="row.id">
<Row row="row" isSelected="row.id === state.selectedRowId" onSelect.bind="selectRow" onRemove.bind="removeRow"/>
</t>
</tbody>
</table>
<span class='preloadicon glyphicon glyphicon-remove' aria-hidden="true" />
</div>\`;
static components = { Button, Row };
setup() {
this.state = useState({
rows: [],
selectedRowId: null
});
this.benchmarking = false;
onPatched(() => {
if (this.benchmarking) {
this.stop();
}
});
}
start(descr) {
this.benchmarking = \`[\${descr}]\`;
console.time(this.benchmarking);
}
stop() {
console.timeEnd(this.benchmarking);
this.benchmarking = false;
}
run() {
this.start('add1000');
this.state.rows = buildData(1000);
this.state.selectedRowId = null;
}
runLots() {
this.start('add10_000');
this.state.rows = buildData(10_000);
this.state.selectedRowId = null;
}
add() {
this.start('append1000');
this.state.rows = this.state.rows.concat(buildData(1000));
}
update() {
this.start('update1/10th');
let index = 0;
const rows = this.state.rows;
while (index < rows.length) {
rows[index].label = rows[index].label + " !!!";
index += 10;
}
}
clear() {
this.start('clear');
this.state.rows = [];
this.state.selectedRowId = null;
}
swapRows() {
this.start('swap');
const rows = this.state.rows;
if (rows.length > 998) {
let tmp = rows[1];
rows[1] = rows[998];
rows[998] = tmp;
}
}
selectRow(id) {
this.start('select');
this.state.selectedRowId = id;
}
removeRow(id) {
this.start('remove1');
const rows = this.state.rows;
rows.splice(rows.findIndex(row => row.id === id), 1);
}
}
mount(Root, document.body, { templates: TEMPLATES, dev: true });
`;
const BENCHMARK_CSS = `
tr.danger {
font-weight: bold;
}
.remove:hover {
font-weight: bold;
}
.remove {
cursor: pointer;
}
`;
export const SAMPLES = [
{
description: "Components",
@@ -1625,4 +1810,9 @@ export const SAMPLES = [
xml: WMS_XML,
css: WMS_CSS,
},
{
description: "Benchmark example",
code: BENCHMARK_JS,
css: BENCHMARK_CSS,
},
];