-
Notifications
You must be signed in to change notification settings - Fork 151
Table and Layout Tutorial, Part 2: Resources and Selectors
Part 1: The Goal
Part 2: Resources and Selectors
Part 3: Simple Transformations
Part 4: Duplicating Elements and Nested Transformations
Part 5: Frozen Transformations, Including Snippets and Templates
(Comments to Brian Marick, please.)
At this point, I recommend you download a version of the Critter4Us app as an easy way to get HTML files. Alternately, you can look at tutorial layout HTML and tutorial herd HTML.
Enlive obeys the classpath when looking for what it calls resources. In our case, resources are sources of HTML text. That encourages me to put HTML files right next to the code that processes them:
In order learn about loading up a resource, do this:
1429 $ lein repl
REPL started; server listening on localhost port 53116
jcrit.server=> (use 'clojure.pprint)
nil
jcrit.server=> (use 'net.cgrand.enlive-html)
nil
jcrit.server=> (def herd (html-resource "jcrit/views/herd_changes.html"))
#'jcrit.server/herd
jcrit.server=> (def layout (html-resource "jcrit/views/layout.html"))
#'jcrit.server/layout
Once a resource has been loaded, what does it look like?
jcrit.server=> (pprint herd)
({:tag :html,
:attrs nil,
:content
({:tag :body,
:attrs nil,
:content
({:tag :form,
...
Enlive nodes are maps with keys :tag
, :attrs
, and :content
. Content values are lists with a combination of strings and nodes:
({:tag :form,
:attrs {:method "post", :action "/herd-changes"},
:content
("\n " ;; A string
{:tag :table,
:attrs nil,
...
Enlive functions that work on nodes can take either a single node or a sequence of nodes.
We can use selectors to grab pieces of node trees. A selector is enclosed in square brackets. If you want to look at all trees rooted at the <div>
tag in the tutorial layout HTML, you'd type this:
jcrit.server=> (pprint (select layout [:div]))
({:tag :div,
:attrs {:id "wrapper"},
:content ("\n " {:type :comment, :data "body"} "\n ")})
The result is a single-element list of matching nodes from the tutorial layout HTML. Like jQuery, the number of matches doesn't matter unless you need it to.
In the tutorial herd HTML, there's one <tr>
with class per_animal_row
and one without any class. We can select the first into a single-element list like this:
jcrit.server=> (pprint (select herd [:tr.per_animal_row]))
({:tag :tr,
:attrs {:class "per_animal_row"},
:content
The notation should look familiar from CSS.
Later, we'll need to select the <div>
with id wrapper
from the tutorial layout HTML. That could done like this:
jcrit.server=> (pprint (select layout [:div#wrapper]))
({:tag :div,
:attrs {:id "wrapper"},
:content ("\n " {:type :comment, :data "body"} "\n ")})
Since the id is unique, we don't actually need the <div>
:
jcrit.server=> (pprint (select layout [:#wrapper]))
({:tag :div,
:attrs {:id "wrapper"},
:content ("\n " {:type :comment, :data "body"} "\n ")})
Note the colon at the front of :#wrapper
: the value must be a symbol.
The selector can also contain function calls. If we wanted to select the element from the tutorial herd HTML that has a type
attribute of text
, we'd do this:
jcrit.server=> (pprint (select herd [(attr= :type "text")]))
({:tag :input,
:attrs {:name "true-name", :class "true-name", :type "text"},
:content nil}
{:tag :input,
:attrs
{:name "extra-display-info",
:class "extra-display-info",
:type "text"},
:content nil})
Similarly, we could select any <option>
tag whose selected
attribute is "selected"
, we'd type the following... or would we?
jcrit.server=> (pprint (select herd [:option (attr= :selected "selected")]))
()
We get nothing because that selector means "Narrow the scope to <option>
tags. Within that scope, locate any tag whose selected
attribute is 'selected'
". The way concatenation means scope-narrowing is perhaps clearer if we ask for all <input>
tags within a <tr>
tag whose class is per_animal_row
. If you look at the tutorial herd HTML, that would exclude the submit button at the end of the <table>
. Our query would look like this:
jcrit.server=> (pprint (select herd [:tr.per_animal_row :input]))
({:tag :input,
:attrs {:name "true-name", :class "true-name", :type "text"},
:content nil}
{:tag :input,
:attrs
{:name "extra-display-info",
:class "extra-display-info",
:type "text"},
:content nil})
If we want to select subtrees where the tag is <option>
and the selected
attribute is "selected"
, we have to enclose the two selectors in another pair of square brackets to indicate and:
jcrit.server=> (pprint (select herd [[:option (attr= :selected "selected")]]))
({:tag :option, :attrs {:selected "selected"}, :content ("Bovine")})
Being a Boolean kind of person, discussion of how to do and probably makes you wonder how to do or. Enclose the disjunctions in set-brackets (#{}
):
jcrit.server=> (pprint (select herd [#{:select (attr= :selected "selected")}]))
({:tag :select,
:attrs {:name "species", :class "species"},
:content
("\n "
{:tag :option, :attrs {:selected "selected"}, :content ("Bovine")}
"\n "
{:tag :option, :attrs nil, :content ("Equine")}
"\n ")}
{:tag :option, :attrs {:selected "selected"}, :content ("Bovine")})
I haven't covered all of Enlive's selector variants, but this is enough for us to reach our goal. You can find all the details here.
It wouldn't hurt to have more selector examples, since the selector syntax page introduces a new idea, the state machine, and is also pretty terse.