Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

276 lines (199 sloc) 9.362 kb

Enfocus

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 & Examples

Documentation Site
Example Website

Where do I get support?

On the group

Quick Start

Setup

From any leiningen project file:

[enfocus "1.0.0-alpha2"]

Then make sure you have your lib folder on your classpath.

For the best development experience, use lein-cljsbuild. lein-cljsbuild 0.7.0 includes the third party google libraries needed for enfocus.

If you are using an older version of ClojureScript you will need to use ["0.9.0-SNAPSHOT"].

The Basics

Every great journey starts with “Hello world!”

(ns my.namespace
  (:require [enfocus.core :as ef])
  (:require-macros [enfocus.macros :as em]))

(defn start [] 
  (em/at js/document
    ["body"] (em/content "Hello world!")))

(set! (.-onload js/window) start)         

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:

(at a-node (transform arg1 ...))

and

(at a-node
    [selector1] (transform1 arg1 ...)
    [selector2] (transform2 arg1 ...))

In the first case at is passed a node or node set 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 passed a node or node set and a set of
selector/transform pairs. The selectors are scoped by the node or node set 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

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"] (em/content msg))

(em/defaction setup []
  ["#button1"] (em/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"] (em/chain 
                 (em/resize width :curheight 500 20)
                 (em/resize 5 :curheight 500 20)))

(em/defaction setup []
  ["#button2"] (em/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"] (em/content fruit)
               ["tr > *:last-child"] (em/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"] (em/content "fruit")  
                ["thead tr > *:last-child"] (em/content "quantity")
                ["tbody"] (em/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 :compiled template2 "/templates/template1.html" [fruit-data] 
                ["#heading1"] (em/content "fruit")  
                ["thead tr > *:last-child"] (em/content "quantity")
                ["tbody"] (em/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 function 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]"] (em/content (template2 {"banana" 5 "pineapple" 10}))
             ["#heading1"] (em/set-attr :id "new-heading1"))

Transformations

A transformation is a function that returns either a node or collection of node.

Enfocus defines several helper functions:

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)

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)
  chain              (chain (fade-in ttime) ;serialize async effects
                            (move x y ttime)
                            (fade-out ttime)
                            ...)

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

Contributing

Compile ClojureScript

 git clone git://github.com/ckirkendall/enfocus.git
 cd enfocus/project
 lein deps
 lein cljsbuild once

Viewing the Test Page

 cd enfocus/sample
 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

License

Copyright © 2012 Creighton Kirkendall

Distributed under the Eclipse Public License, the same as Clojure.

Special Thanks!

Christophe Grand for creating enlive and building a simple api for dom manipulation.

Jump to Line
Something went wrong with that request. Please try again.