Consider this example:
(defn todo-view [{:keys [text id done]}]
{:fx/type :h-box
:children [{:fx/type :check-box
:selected done
:on-selected-changed #(swap! *state assoc-in [:by-id id :done] %)
{:fx/type :label
:style {:-fx-text-fill (if done :grey :black)}
:text text}]})There are problems with using functions as event handlers:
- Performing mutation from these handlers requires coupling with that
state, thus making
todo-viewdependent on mutable*state - Updating state from listeners complects logic with view, making application messier over time
- There are unnecessary reassignments to
on-selected-changed: functions have no equality semantics other than their identity, so on every change to this view (for example, when changing it's text),on-selected-changedwill be replaced with another function with same behavior.
To mitigate these problems, cljfx allows to define event handlers as
arbitrary maps, and provide a function to an app that performs actual
handling of these map-events (with additional :fx/event key containing
dispatched event):
;; Define view as just data
(defn todo-view [{:keys [text id done]}]
{:fx/type :h-box
:spacing 5
:padding 5
:children [{:fx/type :check-box
:selected done
:on-selected-changed {:event/type ::set-done :id id}}
{:fx/type :label
:style {:-fx-text-fill (if done :grey :black)}
:text text}]})
;; Define single map-event-handler that does mutation
(defn map-event-handler [event]
(case (:event/type event)
::set-done (swap! *state assoc-in [:by-id (:id event) :done] (:fx/event event))))
;; Provide map-event-handler to app as an option
(cljfx/mount-app
*state
(cljfx/create-app
:middleware (cljfx/wrap-map-desc assoc :fx/type root)
:opts {:fx.opt/map-event-handler map-event-handler}))