Enfocus
DOM manipulation and templating library for ClojureScript inspired by Enlive.
Install / Use
/learn @ckirkendall/EnfocusREADME
Enfocus
Enfocus is a DOM manipulation and templating library for
ClojureScript. Inspired by Christophe Grand's [Enlive][1], 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][2]
Another [tutorial][3] by Brian Marick.
Documentation & Examples
[Documentation & Demo Site][4]
[Example Website][5]
Where do I get support?
[On the group][6]
Quick Start
Setup
From any leiningen project file:
For the best development experience, use [lein-cljsbuild][7]
The Basics
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 enfocus!")))
(set! (.-onload js/window) start)
See [hello-enfocus][15] repo.
The at form
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][8]
Handling Events
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.
Effects
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)
Actions, templates and snippets
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][11] style emitters introduced in [enlive "1.1.0"][1].
(defn hiccup-template [arg1]
(ef/html
[:h1#hiccup.clazz {:width arg1} "Hiccup Emitters are Cool"]))
Transformations
A transformation is a function that returns either a node or collection of node.
Enfocus defines several helper functions for transformations:
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-listeners (remove-listeners :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
Selectors
Enfocus supports both [CSS3][8] and [XPath][12] 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
The from form is how we get 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 ...))
Extractors
An extractor is a function that takes a node and returns information about.
Enfocus defines several helper fuctions for extractors:
An extractor is a funciton that takes a node and returns information a
