-
Notifications
You must be signed in to change notification settings - Fork 0
Event dispatching
We sometimes write code like this:
(listen button
"click"
(process-form form-data))
When the button is clicked we process the form data. The problem with this code is that it is too specific. We are conflating the "who" and the "when" with the "what". All we need to do here is report what happened. We can decide how to respond to this later.
Using events, we might change the above code to look like this:
(listen button
"click"
(fire :greeting-form-submit form-data))
Here we are only reporting what happened. We are free to respond to this event in any number of ways without modifying this code. We can add logging, record events for later playback or update any part of the UI.
The namespace one.dispatch
in the file src/lib/cljs/one/dispatch.cljs
provides an implementation of a simple event dispatching system.
Events may be fired with an event-id and, optionally, data. Both the event-id and the passed data are arbitrary Clojure values. Reactor functions can be set up to react to specific events. Reactor functions are passed the event-id and the event data.
To see how this works, let's go through some examples interacting with the application from the REPL. To follow along, start a ClojureScript REPL using lein repl
followed by (go)
if you've not started one, and connect to the sample application. With an active REPL, move into the one.dispatch
namespace.
(in-ns 'one.dispatch)
Before we fire any events, we should set up a reaction. A reaction is created with the react-to
function. The reaction below will show an alert displaying the message data sent in the event.
(def alert-reaction
(react-to #{:test-event} (fn [event-id data] (js/alert data))))
react-to
takes an event predicate function and a reactor function and returns a reaction. When an event is fired, the event-id will be passed to the event predicate function. If this function returns true
, then the reactor function will be called passing the event-id
and data
.
In the example above, the predicate function is a set. Sets are functions which take a value and return that value if it is in the set and nil
if it is not in the set.
We can test this by firing an event, as shown below.
(fire :test-event "foo")
The fire
function takes an event-id
and, optionally, a value. Both the event-id
and the data
can be any ClojureScript value.
The reaction that is returned from the call to react-to
may be used to delete the reaction.
(delete-reaction alert-reaction)
If we now try to fire the same event that was fired above, we will notice that nothing happens.
Sometimes we would like to react to something a specific number of times. For example, we may have some resource that we want to clean up whenever the state of the application changes. The three argument version of react-to
takes a number as its first parameter. This is the number of times that the reaction is run before being deleted.
(react-to 1 #{:one-time} (fn [event-id data] (js/alert data)))
(fire :one-time "foo")
(fire :one-time "bar")
In the above example, the reaction is set up to run once. The first fired event will be displayed in an alert box and the second will not.
Even though the sample application is small, there are a lot of interesting uses of events.
The input form provides instant feedback for every state of the form and for every state transition. For example, when data is entered in a field, the value will be validated and the form will display an error message. Events are used to keep the validation code out of the view.
Views are rendered when application state changes. Events are used to report that application state has changed.
We can view all fired events by enabling logging. To do this, evaluate the following form:
(one.logging/start-display (one.logging/console-output))
This will cause all events to be logged to the JavaScript console. In the browser, open a console and interact with the application. Each fired event will be printed to the console.
Reactions may be added anywhere in the application, allowing new features to be added without modifying existing code.
Both history and logging are implemented in this way. See src/app/cljs/one/sample/logging.cljs
and src/app/cljs/one/sample/history.cljs
for more information.