Reputation: 310
I want to write a program which adds complex behavior to a div (think some kind of interactive graphic, for example). I would like to be able to pass a div to a function in the library and have the library add behavior to the div.
If I were doing this in JavaScript, I would write the following.
In my_page.html
:
<div id="program-container"></div>
<script src="my_library.js" type="text/javascript"></script>
<script type="text/javascript">
useDivForACoolProgram(document.getElementById("program-container"));
</script>
In my_library.js
:
function useDivForACoolProgram(div) {
var x = 2;
var y = 3;
function setup() {
div.innerHTML = "Getting ready to run...";
doMainSetup();
}
function doMainSetup() {
...
// Lots more functions, many of which refer to div
}
Note that the library exposes a single function which accepts a div. When we pass it a div, the library keeps all of its state in a closure associated with the passed div, which would potentially allow me to add this behavior to many divs on the page if I were so inclined.
I want to do the same sort of thing in ClojureScript. My first attempt was as follows:
(defn use-div-for-a-cool-program [div]
(def x 2)
(def y 3)
(defn setup []
(set! (.innerHTML div) "Getting ready to run...")
(do-main-setup))
(defn do-main-setup []
...
;; Lots more functions, many of which refer to div
)
But this doesn't work, because def
and defn
define variables at the module's scope rather than defining variables local to use-div-for-a-cool-program
. If I were to call use-div-for-a-cool-program
multiple times with different divs, all of the new def
s and defn
s would override the old each time.
One solution would be to use let
instead, but this is a bit unsatisfying because it forces us to give the implementation of functions before they can be referenced and is also hard to read, e.g.
(defn use-div-for-a-cool-program [div]
(let [x 2
y 3
do-main-setup (fn []
...)
setup (fn []
(set! (.innerHTML div) "Getting ready to run...")
(do-main-setup))
;; Lots more functions, many of which refer to div
]
(setup)))
Is there a better solution?
Upvotes: 0
Views: 158
Reputation: 1521
My solution is not what you want to hear :) You pass div
as an argument to each function. Instead of coupling the functions to div
implicitly you specify explicitly that a div is needed:
(defn do-main-setup [div]
...)
(defn setup [div]
(set! (.innerHTML div) "Getting ready to run...")
(defn use-div-for-a-cool-program [div]
(let [x 2
y 3]
(do-main-setup div))
;; Lots more functions, many of which refer to div explicitly
(setup div)))
Even if this is a little bit more verbose (as you pass div
every time) it makes your intent clear. I use a closure when I want to return the function and call it later, remembering the scope in which it was defined. I don't see how it helps to define those functions as closures and call them precisely after.
If you want to keep some state associated to your div
I would also do so explicitly. For example, if you want to keep count of how many clicks your div received I would do this:
(defn add-state-handler [div state]
(set! (.onclick div) #(swap! state inc)))
(defn use-div-for-a-cool-program [div]
(let [x 2
y 3
counter (atom 0)]
(do-main-setup div))
(add-state-handler div counter)
;; Other functions that reference div and counter
(setup div)))
In short, I would avoid handling any state or mutable values (counter
or div
) implicitly. If you have to render a view that depends on some changing state I would recommend any Clojurescript React wrapper like Om or Reagent.
Upvotes: 1