Composing snippets

318 views
Skip to first unread message

Miikka Miettinen

unread,
Mar 5, 2011, 10:48:13 AM3/5/11
to enliv...@googlegroups.com
Hi,

I'm experimenting with Enlive, and it's really great! Thanks a lot for creating such an ingenious library!

However, I have at least one need that it doesn't seem to address "out of the box". Suppose a web designer has created me a bunch of nice user interface components, which have sections of identical markup. For example, there could be a list of labeled radio buttons along with another list which is the same except for a text field in the end for "other". I'd like to avoid duplicating the common part, but defsnippet apparently requires me to define the nodes and the transformations together.

So, the markup could be something like this:
<html>
<body>
<div id="labeled-choices-without-other">
<!-- Static stuff here -->
<div class="text">Text</div>
<input type="radio" name="a" value="1"><label>Choice</label><br>
<!-- Static stuff here -->
</div>
<hr>
<div id="labeled-choices-with-other">
<!-- Static stuff here -->
<div class="text">Text</div>
<input type="radio" name="b" value="1"><label>Choice</label><br>
<input type="radio" name="b" value="2"><label>Something else</label><input name="c" size="10">
<!-- Static stuff here -->
</div>
</body>
</html>

Creating the second type of radio button list (#labeled-choices-with-other) would be exactly like creating the first type (#labeled-choices-without-other), except for the additional step of transforming the last row. I think I managed to get rid of the duplication (at least in this toy example) with two additional macros:

;; like defsnippet, but the nodes aren't specified yet
(defmacro deftransfs [name args & forms]
`(def ~name
(fn [nodes# ~@args]
(flatmap (transformation ~@forms) nodes#))))

;; fixes the nodes to transform and combines the transformations
(defmacro compose-snippet [name source selector & transfs]
(let [args (gensym "args")]
`(def ~name
(fn [~args]
~(reduce
#(list %2 %1 args)
`(~(first transfs) (select (html-resource ~source) ~selector) ~args)
(rest transfs))))))


With these, I can create my "snippets" as follows:

;; list of radio buttons with labels, transformations only
(deftransfs labeled-choices [{:keys [item choices]}]
[:.text] (content item)
{[:input][:br]} (clone-for [x choices]
[:input] (set-attr :name item, :value x)
[:label] (content x)))

;; single radio button & label with a text field, transformations only
(deftransfs other-choice [{:keys [item other]}]
[[:input (nth-last-of-type 2)]] (set-attr :name item, :value other)
[[:label last-of-type]] (content other)
[[:input last-of-type]] (set-attr :name other))

;; associates nodes with one set of transformations
(compose-snippet labeled-choices-without-other
"catalog.html" [:#labeled-choices-without-other]
labeled-choices)

;; associates nodes with two sets of transformations
(compose-snippet labeled-choices-with-other
"catalog.html" [:#labeled-choices-with-other]
labeled-choices other-choice)


Being something of a newbie, I'd like to ask if this is a reasonable approach? If it is, would it by any chance be worthwhile to support something similar in a future version of Enlive?

Best regards,
Miikka

Christophe Grand

unread,
Mar 7, 2011, 8:26:01 AM3/7/11
to enliv...@googlegroups.com
Hi,


On Sat, Mar 5, 2011 at 4:48 PM, Miikka Miettinen <miikka.m...@gmail.com> wrote:
However, I have at least one need that it doesn't seem to address "out of the box". Suppose a web designer has created me a bunch of nice user interface components, which have sections of identical markup. For example, there could be a list of labeled radio buttons along with another list which is the same except for a text field in the end for "other". I'd like to avoid duplicating the common part, but defsnippet apparently requires me to define the nodes and the transformations together.

#'transformation allow you to create a transformation fn which can be used as the only body of #'snippet #template etc. You can chain several fns with do->.

Here is my variant on your code:

 
;; list of radio buttons with labels, transformations only
(deftransfs labeled-choices [{:keys [item choices]}]
 [:.text] (content item)
 {[:input][:br]} (clone-for [x choices]
                            [:input] (set-attr :name item, :value x)
                            [:label] (content x)))

(defn labeled-choices [item choices]
  (transformation

    [:.text] (content item)
    {[:input][:br]} (clone-for [x choices]
                            [:input] (set-attr :name item, :value x)
                            [:label] (content x))))
 

;; single radio button & label with a text field, transformations only
(deftransfs other-choice [{:keys [item other]}]
 [[:input (nth-last-of-type 2)]] (set-attr :name item, :value other)
 [[:label last-of-type]] (content other)
 [[:input last-of-type]] (set-attr :name other))

(defn other-choice [item other]
  (transformation

    [[:input (nth-last-of-type 2)]] (set-attr :name item, :value other)
    [[:label last-of-type]] (content other)
    [[:input last-of-type]] (set-attr :name other)))

 
;; associates nodes with one set of transformations
(compose-snippet labeled-choices-without-other
 "catalog.html" [:#labeled-choices-without-other]
 labeled-choices)

(defsnippet labeled-choices-without-other "catalog.html" [:#labeled-choices-without-other]
 [{:keys [item choices]}]
  (labeled-choices item choices))


;; associates nodes with two sets of transformations
(compose-snippet labeled-choices-with-other
 "catalog.html" [:#labeled-choices-with-other]
 labeled-choices other-choice)

(defsnippet labeled-choices-with-other "catalog.html" [:#labeled-choices-with-other]
 [{:keys [item choices other]}]
  (do-> (labeled-choices item choices) (other-choice item other)))
 
 hth,

  Christophe

Miikka Miettinen

unread,
Mar 7, 2011, 9:11:35 AM3/7/11
to enliv...@googlegroups.com
Great! I thought I might be able to do this without my own macros, but couldn't quite figure out how. Thanks!

--Miikka


--
You received this message because you are subscribed to the Google Groups "Enlive" group.
To post to this group, send email to enliv...@googlegroups.com.
To unsubscribe from this group, send email to enlive-clj+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/enlive-clj?hl=en.

Reply all
Reply to author
Forward
0 new messages