Handling Events with Enact
Hello, it’s time to start learning about handling events in Enact!
The @enact/core/handle
module provides a set of utilities to support handling events for kind()
s and React.Component
s. It is also designed to (hopefully) simplify the sometimes-tedious work of complex event filtering, processing, forwarding, etc. by adopting a functional approach.
handle
is a function factory. It accepts one or more functions that each receive at least an event (ev
) and props
from the originator of the event. Theses functions will be called, in order, until one returns false
(or any falsy value). To make testing easier, treat the functions as pure functions that only operate on their arguments and return a value.
import handle from '@enact/core/handle';
const logClick = (ev, props) => {
console.log('CLICK!', ev, props);
};
const myClickHandler = handle(logClick);
While the above code gives a working example, it isn’t very interesting. There is only one input function provided and no processing or filtering of the event is being performed. We’ll add some more utility to it, but first a short bit about how this handler can be used with both kind()
s and React.Component
s.
One Handler, Two Sources
The handler needs to be bound to a component instance in order for it to receive that component’s props.
kind()
With kind()
s, the binding is done automagically by using the handlers
configuration object:
import Button from '@enact/moonstone/Button';
import kind from '@enact/core/kind';
const MyButton = kind({
name: 'MyButton',
handlers: {
// `MyButton` will get `onClick` as a prop from Enact automagically
onClick: myClickHandler
},
render: ({...rest}) => (
<Button {...rest} />
)
});
...
<MyButton
source="Enact" // this is not a valid DOM attribute, but we're using it to set up a later example
>
Click me!
</MyButton>
...
NOTE: Astute observers will notice that
'source'
is not a valid DOM attribute for buttons. In this case, we're going to use it to set up a later example that looks at component props.
When the handler is called, it will receive the onClick
event payload and the props of MyButton
.
React.Component
handle
appends a utility method (bindAs
) to the handler it returns that makes it easy to use with React.Component
s:
import handle from '@enact/core/handle';class MyOtherButton extends React.Component {
constructor (props) {
super(props);
// Will bind `myClickHandler` to `this` and set as this.myClickHandler
// Additionally, will add `myClickHandler` as the function name displayed in dev tool call stacks
myClickHandler.bindAs(this, 'myClickHandler');
}
render () {
return (
<button
onClick={this.myClickHandler}
>
{this.props.children}
</button>
);
}
}
...
<MyOtherButton
source="React"
>
No, click me!
</MyOtherButton>
...
As with the kind()
example, when the button is clicked, the handler will receive the onClick
event payload and the props of MyOtherButton
.
Sample
This sample demonstrates the simple case of using the same handler with a kind()
and aReact.Component
:
Event Filtering
The example handler uses a single input function. Let’s see what we can do with more than one. handle
provides additional utilities for processing or filtering events and we can also write our own, as we well know. We can add some to our import:
import {forProp, handle, log} from '@enact/core/handle'; // `handle` is also a named export!
log
looks like an interesting one! It will log the event, props
, and optionally prepend a custom message. As a bonus, it also only logs when running in development mode. We can eliminate the logClick
function with it, so myClickHandler
now looks like this:
const myClickHandler = handle(log('CLICK!'));
forProp
lets us filter the event handling based on whether a prop exists and matches a provided value. If either the prop does not exist or does not match the value, forProp
does not return true
and handling will stop. Using the example buttons, we might stop handling before the log message unless the event came from the kind
component:
const myClickHandler = handle(
forProp('source', 'Enact'),
log('CLICK!')
);
Of course, that isn’t very useful because we could just not specify that handler for MyOtherButton
. We could instead import and use oneOf
and perform different logging for each type of component.
const myClickHandler = handle(
oneOf(
[forProp('small', true), log('that button was small')], // try it out with `<MyButton small ...>`!
[forProp('source', 'Enact'), log('Enact kind()!')],
[forProp('source', 'React'), log('React.Component!')]
)
);
Sample
This sample shows one way to use Enact’s event filtering capabilities:
Event Forwarding
handle
can easily forward events to other event handlers as well using forward
. This is especially useful if a consumer of the component provides a handler for an event the component already handles. In the button examples, that would be onClick
. forward
looks for the named event in props
and always returns true regardless of its existence. The return value of the forwarded handler is also ignored by the input chain so that handling will continue.
import {forward, handle, log} from '@enact/core/handle';const myClickHandler = handle(
forward('onClick'), // if a consumer specifies `onClick` for the component, this will ensure the event is forwarded to it
log('CLICK!')
);
...
<MyButton
onClick={someOtherHandler}
>
Click me!
</MyButton>
...
Sample
This sample demonstrates forwarding events to other event handlers:
Nice! But, what happens if the overriding handler (someOtherHandler
) is expecting different payload keys or value types? The adaptEvent
method lets us modify (or even replace) the event before passing it to another handler.
import {adaptEvent, forward, handle, log} from '@enact/core/handle';const myClickHandler = handle(
forward('onClick'), // if a consumer specifies `onClick` for the component, this will ensure the event is forwarded to it
adaptEvent((ev) => ({...ev, foo: 'bar'}), forward('onClick')),
log('CLICK!')
);const fooFinder = ({foo}) => {
if (foo) {
console.log('FOUND FOO:', foo);
} else {
console.log('NO FOO FOUND');
}
};
...
<MyButton
onClick={fooFinder}
>
Click me!
</MyButton>
...
Sample
This sample demonstrates how to adapt an event before forwarding to another handler. The onClick
handler will behave differently between the first forward
and the one passed as an argument to adaptEvent
. The adapted event is only valid in the scope of adaptEvent
, so the final log
will only contain the original event (i.e. without the foo
prop).
Even More Event Handling
There are many other utilities for handling events. The examples shown here are just a small sample of functionality. Hopefully, we’ve made it easier to implement the basics and inspired thoughts on what else is possible. For a complete list of methods, see the @enact/core/handle
docs.
Thanks for reading!