3

I'm struggling with understanding how om uses apply to render list items, as shown in the example below taken from the Om tutorial page.

(om/root
  (fn [data owner]
    (om/component 
      (apply dom/ul nil
        (map (fn [text] (dom/li nil text)) (:list data)))))
  app-state
  {:target (. js/document (getElementById "app0"))})

My understanding of apply is that it takes a function and a list of items and apply that function to the list. But in this example that understanding would translate to applying dom/ul to nil.

The things I don't understand are:

  • Why am I applying dom/ul to a list, I don't want to create ul elements, I want to create li elements?
  • How does apply handle all the parameters sent in this example?
Daniel Compton
  • 13,878
  • 4
  • 40
  • 60
user3139545
  • 6,882
  • 13
  • 44
  • 87

2 Answers2

2

dom/ul is a function that produces a React component that will render the corresponding DOM element. The first argument to dom/ul specifies the attributes that you want the DOM element to have. For example, if you wanted dom/ul to have the class attribute set to "main", you would do the following:

  (apply dom/ul #js {:className "main"}
    (map (fn [text] (dom/li nil text)) (:list data)))

Passing nil to the first argument of dom/ul simply sets no DOM attributes.

Clojure apply invokes dom/ul by prepending all arguments (after the dom/ul argument) to the final list (produced by map) argument and passing the result as arguments to dom/ul.

As an example, let's say data had the following value:

["Apple" "Bird"] 

then dom/ul in your example would eventually be invoked as:

(dom/ul nil
    (dom/li nil "Apple")
    (dom/li nil "Bird"))

which would result in the following HTML being rendered :

<ul>
  <li>Apple</li>
  <li>Bird</li>
</ul>
Symfrog
  • 3,398
  • 1
  • 17
  • 13
2

When I first started learning Om, this took me a while to understand. The key thing to remember is that every om.dom function takes a JS map of attributes as it's first parameter, and then any number of om.dom arguments afterwards. What you might be expecting as I did, was that om.dom functions would take attributes and a vector of dom elements. Once this clicked, a lot of Om's design made more sense to me.

Clojure's apply "unwraps" a collection and passes the elements of the collection as arguments to the function being applied. So the intermediate steps of our computation are:

(apply dom/ul nil
    (map (fn [text] (dom/li nil text)) (:list data)))

then

(apply dom/ul nil
    '((dom/li nil "Hi") (dom/li nil "there")))

then

(dom/ul nil
    (dom/li nil "Hi") (dom/li nil "there"))

Without the apply, we wouldn't be passing the right arguments to dom/ul. You will also see apply being used with om/build-all which returns a collection like map.

One more example to extend this, if we wanted to put two dom/ul's inside a dom/p, it would look like this in expanded form:

(dom/p nil
    (dom/ul nil (dom/li nil "Hi") (dom/li nil "there"))
    (dom/ul nil (dom/li nil "Next") (dom/li nil "list")))
Daniel Compton
  • 13,878
  • 4
  • 40
  • 60