Repairs event handling with focus of manipulation and filtering #861
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Current State
fritz2 has supported event-handling since its beginning, but with the release of RC1, we introduced dedicated handling for the internal DOM-API event manipulation with
stopPropagation
,stopImmediatePropagation
orpreventDefault
(see also PR #585). These were part of theListener
-class and implemented as kind ofbuilder
-pattern to provide a fluent API:Those three functions were also provided with extensions on
Flow<T>
in order to make them callable, like shown above, as an intermediateFlow
-operation:Caution
This did not work reliably! It might sometimes work as desired, sometimes it might fail.
The big fundamental problem with Kotlin's
Flow
s is that theemit - collect
-cycle comes with a time delay which can be crucial for operations like the above. The browser's rendering engine keeps on working while the firstEvent
-value is emitted and consumed on its way to thehandledBy
-collector. So it might have already been propagated further, or the default action has already been triggered, by the time those functions are applied. This leads to unwanted und often volatile effects during the application lifecycle.Solution
To bypass the flow-delay, we needed a way to apply those event-manipulating functions as soon as the DOM calls the registered listener function. This now happens inside a new function called
subscribe
with the following signature:In this function, we emit the
Event
-object from the DOM into aFlow
, which can then be processed further.Until the
emit
-function is called, all processing is strictly sequential, which means that the order of the registered event-listeners is guaranteed.So in short, the solution is to apply those functions right onto the
Event
-object before emitting it to the flow.In order to enable this, we have introduced two factory functions for each predefined (property based)
Listener
-object. For example, for theclicks
-Listener, the following two additional factories exist:property
itself:clicks(init: MouseEvent.() -> Unit)
If
-suffix like this:clicksIf(selector: MouseEvent.() -> Boolean)
Those two factories enable a user to control the further processing besides the custom written
Flow
-Handler
-binding.The first is a substitution for simply calling event manipulating functions, the second enables filtering the event-emitting based on the
Event
-object. This is a common pattern used inside the headless components.Now it is possible to write code like this:
The last
click
upon the outer<div>
-tag will never be processed, due to the call of thestopPropagation
inside the<button>
-tag.The filtering works like this:
Important
We strongly recommend to manipulate the event handling only inside the lambda-expressions of the new
Listener
-factory-functions! Strive to move such manipulations from inside some mapping / filtering into such aninit
orselector
-lamba.Further Improvements
Listener
-type. It remains more or less a marker type in order to dispatch the convenience functions to grab values out of specific DOM elements.@ignore
from one test case as the cause is now fixedKey
-APIMigration Guide
If the compiler complains about a missing function, we recommend to switch to the same named factory (with the
init
-parameter):Besides compiler errors, there might also be code sections which should be refactored in order to behave reliably:
Use the
If
-suffix based event-factories to replace such calls: