Enfocus is a DOM
manipulation and templating library for ClojureScript. Inspired by Christophe Grand’s Enlive, Enfocus’ primary purpose is providing a base for building rich interfaces in ClojureScript.
If you are unfamiliar with enlive I also recommend taking a look at these links.
David Nolen wrote a nice tutorial
Another tutorial by Brian Marick.
Documentation & Demo Site
Example Website
From any leiningen project file:
[enfocus "2.0.0-SNAPSHOT"] – experimental use at your own risk[enfocus "1.0.1"] – stable
Then make sure you have your lib folder on your classpath.
For the best development experience, use lein-cljsbuild.
Every great journey starts with “Hello world!”
(ns my.namespace (:require [enfocus.core :as ef] [enfocus.events :as events] [enfocus.effects :as effects]) (:require-macros [enfocus.macros :as em])) (defn start [] (ef/at js/document ["body"] (ef/content "Hello world!"))) (set! (.-onload js/window) start)
At the core to understanding Enfocus is the at
form used in the
“Hello world!” example above. It comes in two basic flavors listed below:
A single transform
(at a-node (transform arg1 ...)) ;or with implied js/document (at selector (transform arg1 ...))
and a series of transforms
(at a-node [selector1] (transform1 arg1 ...) [selector2] (transform2 arg1 ...)) ;or with implied js/document (at [selector1] (transform1 arg1 ...) [selector2] (transform2 arg1 ...))
In the first case at
is passed a node, node set or selector and a transform. This form of at
calls the transform on each element in the node set.
A transform
is nothing more than a function that takes a set of arguments and returns a function
that takes a set of nodes. In case of the “Hello World!” example above, we see the use of
(em/content “Hello world!”) this call returns a function that takes node or node set
and replaces the content with “Hello world!”
In the second case, we see at
is optionally passed a node or node set and a set of
selector/transform pairs. The selectors are scoped by the node, node set or js/document,
if a node is not passed in, and the results of each selector is passed on to its partner transformation.
A selector
is a string representing a CSS3 compliant selector
Enfocus has event handling. Below is a simple example to add an onclick
event handler to a button.
(em/defaction change [msg] ["#button1"] (ef/content msg)) (em/defaction setup [] ["#button1"] (events/listen :click #(change "I have been clicked"))) (set! (.-onload js/window) setup)
The defaction
construct is use here instead defn
. defaction
creates a function that calls the at
form like discussed above
and passes in js/document as the node to be transformed.
Enfocus has the concept of effects. Effects are nothing more than transformations
over a period of time. Below is a simple example of a resize effect. Notice how the
effects can be chained.
(em/defaction resize-div [width] ["#rz-demo"] (effects/chain (effects/resize width :curheight 500 20) (effects/resize 5 :curheight 500 20))) (em/defaction setup [] ["#button2"] (events/listen #(resize-div 200))) (set! (.-onload js/window) setup)
A snippet is a function that returns a seq of nodes, it can be used as a
building block for more complex templates or actions.
You define a snippet by providing a remote resource, a selector and series of transformations.
The snippet definition below selects a table body from the remote resource
templates/template1.html and grabs the first row. It then fills the content of the row.
(em/defsnippet snippet2 "templates/template1.html" ["tbody > *:first-child"] [fruit quantity] ["tr > *:first-child"] (ef/content fruit) ["tr > *:last-child"] (ef/content (str quantity)))
A template is very similar to a snippet except it does not require a selector to
grap a sub section, instead the entire remote resource is used as the dom.
If the remote resource is a full html document only what is inside the body tag is
brought into the template.
(em/deftemplate template2 "/templates/template1.html" [fruit-data] ["#heading1"] (ef/content "fruit") ["thead tr > *:last-child"] (ef/content "quantity") ["tbody"] (ef/content (map #(snippit2 % (fruit-data %)) (keys fruit-data))))
Normally, snippets and templates are loaded via an AJAX request, but you can
also create :compiled
templates, which will be inlined in to resulting code
at compile time:
(em/deftemplate template2 :compiled "/templates/template1.html" [fruit-data] ["#heading1"] (ef/content "fruit") ["thead tr > *:last-child"] (ef/content "quantity") ["tbody"] (ef/content (map #(snippit2 % (fruit-data %)) (keys fruit-data))))
If, snippets and/or templates are loaded via AJAX it is important to make sure the
content has been loaded before calling the template or snippit function. Enfocus
provides a convient macro that works like an onload callback but for AJAX driven
snippets and templates.
(em/wait-for-load (render-page))
An action is a set of transforms that take place on the live dom. below is a
definition of a an action.
(em/defaction action2 [] [".cool[foo=false]"] (ef/content (template2 {"banana" 5 "pineapple" 10})) ["#heading1"] (ef/set-attr :id "new-heading1"))
Enfocus also support hiccup style emitters introduced in enlive 1.1.0.
(defn hiccup-template [arg1] (ef/html [:h1#hiccup.clazz {:width arg1} "Hiccup Emitters are Cool"]))
A transformation is a function that returns either a node or collection of node.
Supported Enlive Transformations
content (content "xyz" a-node "abc") html-content (html-content "<blink>please no</blink>") set-attr (set-attr :attr1 "val1" :attr2 "val2") remove-attr (remove-attr :attr1 :attr2) add-class (add-class "foo" "bar") remove-class (remove-class "foo" "bar") do-> (do-> transformation1 transformation2) append (append "xyz" a-node "abc") prepend (prepend "xyz" a-node "abc") after (after "xyz" a-node "abc") before (before "xyz" a-node "abc") substitute (substitute "xyz" a-node "abc") clone-for (clone-for [item items] transformation) or (clone-for [item items] selector1 transformation1 selector2 transformation2) wrap (wrap :div) or (wrap :div {:class "foo"}) unwrap (unwrap) replace-vars (replace-vars {:var1 "value" :var2 "value")
New Transformations
focus (focus) blur (blur) set-prop (set-prop :value "testing") set-style (set-style :font-size "10px" :background "#fff") remove-style (remove-style :font-size :background) listen (listen :mouseover (fn [event] ...)) remove-listener (remove-listener :mouseover :mouseout) fade-in (fade-in time) or (fade-in time callback) or (fade-in time callback accelerator) fade-out (fade-out time) or (fade-out time callback) resize (resize width height ttime) or (resize width height ttime callback) or (resize width height ttime callback accelerator) move (move x y ttime) or (move x y ttime callback) or (move x y ttime callback accelerator) scroll (scroll x y ttime) or (scroll x y ttime callback) or (scroll x y ttime callback accelerator) chain (chain (fade-in ttime) ;serialize async effects (move x y ttime) (fade-out ttime) ...) set-data (set-data key value)
Currently there is one transformation that is supported by Enlive but not Enfocus. (Patches very welcome!!)
move (move [:.footnote] [:#footnotes] content) ;this will be called relocate in enfocus
Enfocus supports both CSS3 and XPath selectors:
(ns example (:require [enfocus.core :as ef]) (:require-macros [enfocus.macros :as em])) (em/defaction action2 [] [".cool[foo=false]"] (ef/content ....)) ;CSS3 (ef/xpath "//tr[@id='1']/th[2]") (ef/set-attr :id "new-heading1")) ;XPATH
The from
form is how we data from the dom in enfocus. It comes in two basic flavors listed below:
This form returns the result of the extractor.
(from a-node (extractor arg1 ...)) ;or with selector (from selector (extractor arg1 ...))
and
This form returns a map of the form {:key1 result1 :key2 result2}
(from a-node :key1 [selector1] (extractor arg1 ...) :key2 [selector2] (extractor arg1 ...)) ;or implied js/documnt (from :key1 [selector1] (extractor arg1 ...) :key2 [selector2] (extractor arg1 ...))
An extractor is a function that takes a node and returns information about.
An extractor is a funciton that takes a node and returns information about it.
get-attr (get-attr :attr1 :attr2) get-text (get-text) get-prop (get-prop :value) get-data (get-data :key1) read-form (read-form) - returns {:field1 ("val1" "val2") :field2 val}
- Download lieningen
git clone git://github.com/ckirkendall/enfocus.git cd enfocus/project lein deps lein cljx lein cljsbuild once
cd enfocus/testing lein deps lein repl =>(use 'ring.adapter.jetty) =>(use 'enfocus.ring) =>(run-jetty app {:port 3000})
Open your browser to:
http://localhost:3000/test.html
Copyright © 2012 Creighton Kirkendall
Distributed under the Eclipse Public License, the same as Clojure.
Christophe Grand for creating enlive and building a simple api for dom manipulation.