Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Table and Layout Tutorial, Part 3: Simple Transformations

srid edited this pageAug 29, 2011 ·12 revisions

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 toBrian Marick, please.)

In this part, you'll learn how totransform one tree structure of Enlive nodes into another. We'll start with this part of thetutorial layout HTML's node:

jcrit.server=> (pprint (select layout [:div#wrapper]))({:tag:div,:attrs {:id"wrapper"},:content ("\n" {:type :comment, :data"body"}"\n")})

The simplest transformation does nothing:

jcrit.server=> (pprint (transform layout [:div#wrapper] identity))({:tag:html,:attrs nil,:content  ("\n"   {:tag:head,:attrs nil,:content   ...     {:tag:div,:attrs {:id"wrapper"},:content ("\n" {:type :comment, :data"body"}"\n")}"\n")}"\n\n")})

transform takes three arguments. The first is an entire tree or sequence of trees. The second is a selector that picks some subtrees to transform. The third is a function that takes a subtree and returns a new one. Notice that the return value is the entire transformed input, not merely the transformed subtrees.

As a next step, let's assume some earlier use ofhtml-resource gave us this list of nodes:

(defpage-content '({:tag:p,:content ("Hi, mom!")}))

Here's atransform that inserts that into the nodes derived from thetutorial layout HTML, putting it inside the<div>:

jcrit.server=> (pprint (transform layout                                  [:div#wrapper]                                  (fn [a-selected-node]                                    (assoc a-selected-node:content page-content))))({:tag:html, ...     {:tag:div,:attrs {:id"wrapper"},:content ({:tag:p,:content ("Hi, mom!")})}"\n")}"\n\n")})

That third argument is a lot of characters to type to mean "replace the content", so Enlive provides an abbreviation:

jcrit.server=> (pprint (transform layout                                  [:#wrapper]                                  (content page-content)))({:tag:html, ...     {:tag:div,:attrs {:id"wrapper"},:content ({:tag:p,:content ("Hi, mom!")})}"\n")}"\n\n")})

It's the same result. Note well that thecontent function, like the other transforming functions you'll see shortly, returnsanother function. Think ofcontent as an abbreviation formake-content-setter.

You can give multiple arguments tocontent. Moreover, if any of the arguments has been wrapped up in a sequential, you needn't bother unwrapping them:

jcrit.server=> (pprint (transform layout                                   [:#wrapper]                                   (content"Say it!"                                            [[[page-content]]]; <== wrapped"Say it again!"                                           page-content)))     ...     {:tag:div,:attrs {:id"wrapper"},:content      ("Say it!"       {:tag :p, :content ("Hi, mom!")}  ; <== unwrapped"Say it again!"       {:tag :p, :content ("Hi, mom!")})}     ...

Thetutorial layout HTML has a<script> tag that should be transformed to contain a particular page's jQuery code. That code's provided as a vector of strings:

(defjquery-content ["$('input.true_name').first().focus();""if (a < b) foo();"])

To insert it into the right place, this suffices:

jcrit.server=> (pprint (transform layout [:script#jquery_code]                                  (content"\njQuery(function() {\n"                                           jquery-content"\n});\n")))   ...     {:tag:script,:attrs {:type"text/javascript",:id"jquery_code"},:content      ("\njQuery(function() { \n""$('input.true_name').first().focus();""if (a < b) foo();""\n});\n")}"\n")}   ...

Notice that the string input is not subject to HTML escapes. (The< stays an<; it doesn't become an&lt.)

emit*

The previous sentence doesn't really provide the right assurance about HTML escapes.transform produces a huge pile of nodes–how do we know that escaping doesn't happenafter those nodes are converted into the string to be sent to the browser in an HTML Response?

Nodes are converted to strings withemit*. Note the plural: the output is a sequence of strings, not a single joined string. So here's the way to see the final result:

jcrit.server=>;; first, stash the transformed valuejcrit.server=> (deftransformed (transform layout                                            [:script#jquery_code]                                           (content"\njQuery(function() {\n"                                                    jquery-content"\n});\n")))#'jcrit.server/transformedjcrit.server=> (print (apply str (emit* transformed)))<!DOCTYPE html><html><head>    <title>Critter4Us</title>    <link type="text/css" rel="stylesheet" href="/css/reset.css" />    <script type="text/javascript" src="/js/jquery.js"></script>    <script type="text/javascript" src="/js/c4.js"></script>    <script type="text/javascript" id="jquery_code">jQuery(function() { $('input.true_name').first().focus();if (a < b) foo();});</script>...

at

We now have the tools to transform thetutorial layout HTML nodes into their final form: first, substitute the page contents, then add the jQuery code:

jcrit.server=> (pprint (-> layout                           (transform [:#wrapper] (content page-content))                           (transform [:#jquery_code]                                       (content"\njQuery(function() {\n"                                               jquery-content"\n});\n"))))({:tag:html, ...     {:tag:script,:attrs {:type"text/javascript",:id"jquery_code"},:content      ("\njQuery(function() { \n""$('input.true_name').first().focus();""if (a < b) foo();""\n});\n")}  ...     {:tag:div,:attrs {:id"wrapper"},:content ({:tag:p,:content ("Hi, mom!")})}"\n")}"\n\n")})

That works, but it's not wildly appealing. Theat macro lets it look better:

jcrit.server=> (pprint                 (at layout                      [:#wrapper]                      (content page-content)                     [:#jquery_code]                     (content"\njQuery(function() {\n"                              jquery-content"\n});")))({:tag:html,  ...     {:tag:script,:attrs {:type"text/javascript",:id"jquery_code"},:content      ("\njQuery(function() { \n""$('input.true_name').first().focus();""if (a < b) foo();""\n});")}  ...     {:tag:div,:attrs {:id"wrapper"},:content ({:tag:p,:content ("Hi, mom!")})}"\n")}"\n\n")})

Note: Don't assume the transformations will be made in a particular order.

Other content transformations

The originaltutorial layout HTML had an important comment inside the wrapper<div>:

<divid="wrapper"><!--body--></div>

We replaced it. How could we retain it? Withappend:

jcrit.server=> (pprint                  (transform layout [:#wrapper]                             (append page-content)))({:tag:html,     {:tag:div,:attrs {:id"wrapper"},:content      ("\n"       {:type :comment, :data"body"}   ; <<== Still there."\n"       {:tag :p, :content ("Hi, mom!")})}"\n")}"\n\n")})

append adds to the end of the selected element's content.prepend adds to the beginning.

(Note: I'm not trying to document all the transformations. See thecore documentation for the complete list. Alternately, look in the source. By the end of this tutorial, I hope the terminology and mechanisms used in the transformation functions will be clear.)

Whole-tag transformations

You can substitute a new element for an old one, replacing the tag as well as the contents:

jcrit.server=> (pprint (at layout                            [:head] (substitute page-content)                           [:body] (substitute page-content)))({:tag:html,:attrs nil,:content  ("\n"   {:tag:p,:content ("Hi, mom!")}"\n"   {:tag :p, :content ("Hi, mom!")}"\n\n")})

Another transformation,after, adds its arguments just after the selected element. It doesn't change the selected element's content, so (for example) you'd useafter to add a new<td> element to a table row.before adds its arguments before the selected element.

You canwrap selected nodes in other tags:

jcrit.server=> (pprint (transform layout [:div#wrapper]                                   (wrap:div                                  {:id"superdiv",:class"wasted space"})))({:tag:html, ...     {:tag:div,;; <<<= new:attrs {:id"superdiv",:class"wasted space"},;; <<<= new:content;; <<<= new      [{:tag:div,:attrs {:id"wrapper"},:content        ("\n" {:type :comment, :data"body"}"\n")}]}

You can alsounwrap to replace a tag with its content:

jcrit.server=> (pprint (transform layout [:div#wrapper] unwrap))({:tag:html, ...   {:tag:body,:attrs nil,:content    ("\n""\n"     {:type :comment, :data"body"}"\n""\n")}"\n\n")})

Notice theunwrap function is given directly. It's unlike other transformation functions, which take arguments and produce new functions that use those arguments.unwrap can stand alone because there's no argument to give it.

Attribute transformations

You can perform a variety of transformations on attributes.

  • Adding one or more attributes
    jcrit.server=> (pprint (transform layout [:div#wrapper]                                      (set-attr:NEWBIE"FRED",:OLDIE"DAWN")))    ...     {:tag:div,:attrs {:OLDIE"DAWN",:NEWBIE"FRED",:id"wrapper"},:content ("\n" {:type :comment, :data"body"}"\n")}    ...
  • Deleting one or more attributes
    jcrit.server=> (pprint (transform layout [:div#wrapper]                                      (remove-attr:id)))    ...     {:tag:div,:attrs {},:content ("\n" {:type :comment, :data"body"}"\n")}    ...
  • Adding one or more whitespace-separated classes
    jcrit.server=> (pprint (transform layout [:div#wrapper]                                      (add-class"highlight""plain-styled")))    ...     {:tag:div,:attrs {:class"highlight plain-styled",:id"wrapper"},:content ("\n" {:type :comment, :data"body"}"\n")}"\n")}    ...
  • Removing one or more classes (no example)

In summary

We now know how to do this:

jcrit.server=> (println                  (apply str                        (emit*                           (at (html-resource"jcrit/views/layout.html")                              [:#wrapper]                              (content page-content)                              [:#jquery_code]                              (content"\njQuery(function() {\n"                                       jquery-content"\n});")))))
<!DOCTYPE html><html><head><title>Critter4Us</title><linktype="text/css"rel="stylesheet"href="/css/reset.css"/><scripttype="text/javascript"src="/js/jquery.js"></script><scripttype="text/javascript"src="/js/c4.js"></script><scripttype="text/javascript"id="jquery_code">jQuery(function(){$('input.true_name').first().focus();if(a<b)foo();;;<<<====});</script></head><body><divid="wrapper"><p>Hi, mom!</p></div>             ;;<<<====</body></html>

That's hardly ideal. Both the boilerplate code and global variables should be factored out into a helper function. We'll see how to create it inPart 5. That's straightforward, though. While we're thinking about transformations, let's get down and dirty with a more complex case.

Part 4: Duplicating Elements and Nested Transformations

Clone this wiki locally


[8]ページ先頭

©2009-2025 Movatter.jp