Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojurescript DOM interface

Is there a Clojurescript library which makes the DOM look like a Clojure data structure ? I found a few libraries like Enfocus which do certain kinds of DOM manipulation, but what I want is to be able to treat the DOM like this:

(get dom id) - returns element called id in dom
(get dom id create-fn) - return element if exists, otherwise creates it
(update-in dom [:body this that] update-fn) - set attribute on a DOM element 
(assoc parent-element id child-element) - associate child element with parent
(conj parent child) - append child element to parent element

and so on

like image 417
Hendekagon Avatar asked Jul 02 '12 06:07

Hendekagon


1 Answers

Clojure data structures are all persistent, but in your example it seems like you want to side effect (i.e., hit the DOM in place to change it).

That's a pretty procedural/imperative approach, so it may be worth stepping back and reformulating the problem in a more functional style. My personal philosophy is to treat "views as data", and model it using Clojure's persistent data structures up until the last minute when I need to render.

Are you familiar with Hiccup? The idea is to represent an HTML or SVG DOM using plain vectors and maps:

[:div {:with "attribute"} "and" [:span "children"]]

which you can construct by composing plain old Clojure functions. In Clojure you can render this into HTML (using the original Hiccup library), but there are at least two ClojureScript libraries that render directly into (potentially existing) DOM structures. Crate is a close port of Hiccup, and Singult has some additional semantics like D3.js-inspired data binding (Singult is actually written in CoffeeScript, so it's usable from plain JavaScript and is faster than Crate).

My C2 library builds data-binding semantics on top of Singult for keeping the DOM in sync with underlying data. Consider this template for a TODO list:

(bind! "#main"
        [:section#main {:style {:display (when (zero? (core/todo-count)) "none")}}
        [:input#toggle-all {:type "checkbox"
                            :properties {:checked (every? :completed? @core/!todos)}}]
        [:label {:for "toggle-all"} "Mark all as complete"]
        [:ul#todo-list (unify (case @core/!filter
                                :active    (remove :completed? @core/!todos)
                                :completed (filter :completed? @core/!todos)
                                ;;default to showing all events
                                @core/!todos)
                              todo*)]])

(taken from C2 TodoMVC implementation). Things like whether or not the "check all" box is checked is derived directly from the underlying data (stored in an atom). Whenever that data changes, the template will be re-run and the dom automatically updated.

The basic idea is to build mappings in the forward direction from your application data to Hiccup data structures, and then let the library take care of syncing the DOM (adding/removing children, attributes, &c.). If you don't have to concern yourself with the state of the DOM (has this already been added? do I need to toggle some class?) then a lot of incidental complexity falls away.

like image 132
Kevin L. Avatar answered Oct 20 '22 04:10

Kevin L.