Created
March 27, 2024 03:48
-
-
Save awwx/ec8e81c2682b0b67e7c0758dd100b3a9 to your computer and use it in GitHub Desktop.
A version of Electric's dom/on! which only needs to attach the event listener on mount and remove on unmount
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; The goal here is to provide a coupling between Electric | |
; and JavaScript event listeners that's more idiomatic on the | |
; JavaScript side: namely, that the event listener can be | |
; attached only once on mount and only needs to be removed on | |
; unmount. | |
; | |
; The strategy is to use e/fn to convert the reactive event | |
; listener fn into a continuous flow of event listener functions, | |
; which we can then sample to get the latest listener function | |
; when an event occurs. | |
(ns event-listener | |
(:require | |
[missionary.core :as m] | |
[hyperfiddle.electric :as e] | |
[hyperfiddle.electric-dom2 :as dom]) | |
#?(:cljs | |
(:require-macros [event-listener]))) | |
;; Calls `act` on each value of discrete flow `f` for side | |
;; effects. Returns a flow of nil's for integration into | |
;; Electric. Is that necessary? I don't know. | |
(defn mforeach [>f act] | |
(m/reductions | |
(fn [_ x] (act x) nil) | |
nil | |
>f)) | |
;; A verbose version of e/dom-listener | |
#?(:cljs (defn verbose-dom-listener [node typ f opts] | |
(prn "add event listener" (.-id node)) | |
(.addEventListener node typ f (clj->js opts)) | |
(fn [] | |
(prn "remove event listener" (.-id node)) | |
(.removeEventListener node typ f)))) | |
;; Attach an event listener to the node on mount and remove | |
;; on unmount. Returns a discrete flow of events. | |
#?(:cljs | |
(defn mdom-listener> [node event-type opts] | |
(m/observe | |
(fn [emit!] | |
(#_e/dom-listener | |
verbose-dom-listener node event-type emit! opts))))) | |
;; Given a discrete flow of events and a continuous flow of | |
;; listener functions (such as produced by | |
;; (e/fn (fn [event] ...))), sample the latest listener function | |
;; at the time an event occurs and call it with the event. | |
(defn mhandle [>events <listener] | |
(mforeach | |
(m/sample vector <listener >events) | |
(fn [[listener event]] | |
(listener event)))) | |
#?(:cljs | |
(defn mon-event [node event-type opts <listener] | |
(mhandle (mdom-listener> node event-type opts) <listener))) | |
(e/defn On! [event-type opts listener] | |
(e/client | |
(new (mon-event dom/node event-type opts (e/fn [] listener))))) | |
(defmacro on! | |
([event-type listener] | |
`(on! ~event-type {} ~listener)) | |
([event-type opts listener] | |
`(On!. ~event-type ~opts ~listener))) | |
(e/defn Main [ring-request] | |
(e/client | |
(let [!state (atom :a) | |
state (e/watch !state)] | |
(binding [dom/node js/document.body] | |
;; some buttons to change the state | |
(dom/div | |
(dom/text "select: ") | |
(e/for [id [:a :b :c]] | |
(dom/button | |
(dom/props {:id (name id)}) | |
(dom/text (name id)) | |
(dom/on! "click" (fn [_] (reset! !state id)))) | |
(dom/text " "))) | |
;; display the state for convenience | |
(dom/pre (dom/text "state: " (pr-str state))) | |
;; demo: event listener has reactive dependency on state, | |
;; but the event listener is only attached once. | |
(dom/div | |
(dom/button | |
(dom/props {:id "click-me"}) | |
(dom/text "Click Me") | |
(on! "click" | |
(fn [event] | |
(println | |
"click" | |
"shift:" (.-shiftKey event) | |
"state:" state))))))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment