I am trying to understund this piece of code from Web Development With Clojure book. Its about clojure script:
(defn message-form []
(let [fields (atom {})]
(fn []
[:div.content
[:div.form-group
[:p "Name:"
[:input.form-control
{:type :text
:name :name
:on-change #(swap! fields assoc :name (-> % .-target .-value))
:value (:name @fields)}]]]
[:p "Message:"
[:textarea.form-control
{:rows 4
:cols 50
:name :message
:on-change #(swap! fields assoc :message (-> % .-target .-value))}
(:message @fields)]]
[:input.btn.btn-primary {:type :submit :value "comment"}]])))
Can anybody explain this part:
#(swap! fields assoc :name (-> % .-target .-value)
especially this: ...(-> % .-target .-value)
[:input {:on-change #(swap! fields assoc :name (-> % .-target .-value)}]
This defines an input and a function that will be called each time the user changes the input field, i. e. types in it. The function will be called with the change event object as argument, which is then bound to %
. The event target is the input field DOM element, which has as value
its text content. The function then changes fields
to contain as :name
that content.
The #(…)
and the %
belong together: #(…)
creates an anonymous function and %
is the name of its parameter.
The ->
is a macro that inverts the usual lispy prefix composition of forms into something resembling postfix composition or "piping": (-> % .-target .-value)
is expanded to (.-value (.-target %))
.
The .-value
notation is JavaScript interop in ClojureScript. It denotes the access of a field "value" of a JavaScript object; (.-value foo)
in ClojureScript is essentially the same as writing foo.value
or foo["value"]
in JavaScript.
In your question swap!
is taking an atom, a function (assoc
) that updates the value the atom stores and subsequent arguments that are given to the function.
Here is another way of achieving the same result:
#(swap! fields (fn [prev] (assoc prev :name (-> % .-target .-value)))
See that :name
and (-> % .-target .-value)
would be the two subsequent arguments in the swap!
invocation of your question, where apply
is used internally to apply these arguments to assoc
. Here there is just an update function - no subsequent arguments.
As you can see prev
is the internal value of the atom that we are going to change and swap!
just takes an 'updating' function, which returns the next value for swap!
to store.
As far as (-> % .-target .-value)
goes, remember you are already in the reader macro, so %
is the first argument to it. The ->
threading macro threads into the first argument of each following function, so %
is given to .-target
and then the result of that is given to .-value
, giving us: (.-value (.-target %))
.
:name
in the map stored at fields is being set to the target value of some value that is being provided!
Going out a little wider %
is a JavaScript event that is provided whenever the text input field is changed - basically whenever the user types text. You rarely want the whole of the event - just the text that has been typed, which is where getting the target of the event (as opposed to the source) and then that target's value, comes in. The .-
part means you are getting a JavaScript property - this is 'interop', as opposed to normal ClojureScript syntax.
#()
is a reader macro. It is a short form for an anonymous function (fn [args] ...)
, where %
is the first argument of this function.
In this particular case #(swap! fields assoc :name (-> % .-target .-value)
is equal to (fn [X] (swap! fields assoc :name (-> X .-target .-value))
->
is a threading macro and (-> X .-target .-value)
is equivalent to (.-value (.-target X))
.
(.-target X)
means get property target
of object X
.
So you define an anonymous function which receives single argument. This function will change the fields
atom so that its key :name
will be set to the value of the property value
of the object in property target
of the object X
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With