What does swap! with assoc and (-> % .-target .-value) syntax mean in ClojureScript?
Asked Answered
A

3

6

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)

Auricula answered 18/8, 2018 at 17:11 Comment(1)
As described in Where should "someone explain this code to me" questions go? on Meta Stack Overflow, such questions are usually too broad to be on-topic here. Please try to make them as narrow and specific as possible before asking, and focus the topic on the exact aspect you're asking about, providing only the shortest possible runnable code that illustrates the details of your question.Lots
I
3

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.

Intreat answered 18/8, 2018 at 17:39 Comment(2)
hi, any document link about ".-"? tried google, didn't find anything about it @Chris MurphyAedes
There probably is, but I just picked up this by reading enough of other people's code. ClojureScript JavaScript interop would be the best thing to Google.Intreat
S
5
[: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.

Sequential answered 18/8, 2018 at 21:58 Comment(0)
I
3

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.

Intreat answered 18/8, 2018 at 17:39 Comment(2)
hi, any document link about ".-"? tried google, didn't find anything about it @Chris MurphyAedes
There probably is, but I just picked up this by reading enough of other people's code. ClojureScript JavaScript interop would be the best thing to Google.Intreat
A
2

#() 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.

Anatolio answered 18/8, 2018 at 17:27 Comment(2)
What is value and what target? And what argument?Auricula
The argument will be the change event. The .- is used to access properties in JS objects so the javascript equivalent is event.target.valueAbott

© 2022 - 2024 — McMap. All rights reserved.