Adapted from image by Rob Faulkner (CC BY 2.0)

Handling Events with Enact

Dave Freeman
Enact JS
Published in
5 min readJun 18, 2019

--

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.Components. 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.Components.

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.Components:

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!

--

--

Dave Freeman
Enact JS

LG Silicon Valley Lab; Enact JavaScript framework and Voice of Enact