Looping Case Question

160 views
Skip to first unread message

David Nolen

unread,
Jan 31, 2010, 2:48:22 PM1/31/10
to Enlive
When using traditional templating solutions it's common that you'll
see something like the following:

{% for x in coll %}
<h>{{ x.heading }}</h>
<p>{{ x.content }}</p>
{% endfor %]

Note that there is no containing element for the <h> and <p> elements.
How would one loop over something like this using Enlive snippets +
templates? If there was a container element it would be simple, but
without it I don't see anything obvious. Is there a way to support
this case easily?

Thanks,
David

Robert Campbell

unread,
Feb 1, 2010, 6:43:01 AM2/1/10
to enliv...@googlegroups.com
Hi David,

I think you're looking for clone-for:

(at node [:option]
(clone-for [option options]
(do-> (set-attr :value (option :value))
#(if (option :selected)
((set-attr :selected "selected") %) %)
(content (option :name)))))))

Here you can see that I only ever reference the <option> element, but
obviously it could be any standalone node.

Rob

> --
> 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.
>
>

Chas Emerick

unread,
Feb 1, 2010, 7:56:47 AM2/1/10
to enliv...@googlegroups.com
Further, go to http://wiki.github.com/cgrand/enlive/getting-started
and search for clone-for, as Rob mentioned.

- Chas

Christophe Grand

unread,
Feb 1, 2010, 4:53:16 PM2/1/10
to enliv...@googlegroups.com
Hi David,

On Sun, Jan 31, 2010 at 8:48 PM, David Nolen <dnolen...@gmail.com> wrote:

> When using traditional templating solutions it's common that you'll
> see something like the following:
>
> {% for x in coll %}
> <h>{{ x.heading }}</h>
> <p>{{ x.content }}</p>
> {% endfor %]
>
> Note that there is no containing element for the <h> and <p> elements.

Indeed there's no facility to transform a group of adjacent nodes
without a container.
Off the top of my head:

Clojure 1.2.0-master-SNAPSHOT
user=> (use 'net.cgrand.enlive-html)
nil
user=> (def src (-> "<h1>Big Title<h2>Smaller title<p>A paragraph<div
class="footer">a footer" java.io.StringReader. html-resource))
java.lang.Exception: Unable to resolve symbol: footer in this context
(NO_SOURCE_FILE:2)
user=> (def src (-> "<h1>Big Title<h2>Smaller title<p>A paragraph<div
class='footer'>a footer" java.io.StringReader. html-resource))
#'user/src
user=> src
({:tag :html, :attrs nil, :content [{:tag :body, :attrs nil, :content
[{:tag :h1, :attrs nil, :content ["Big Title"]} {:tag :h2, :attrs nil,
:content ["Smaller title"]} {:tag :p, :attrs nil, :content ["A
paragraph"]} {:tag :div, :attrs {:class "footer"}, :content ["a
footer"]}]}]})
user=> (apply str (emit* src))
"<html><body><h1>Big Title</h1><h2>Smaller title</h2><p>A
paragraph</p><div class=\"footer\">a footer</div></body></html>"
user=> (defsnippet h+p src [#{:h2 :p}] [{:keys [title text]}] [:h2]
(content title) [:p] (content text))
#'user/h+p
user=> (h+p {:title "hello" :text "world"})
({:tag :h2, :attrs nil, :content ("hello")} {:tag :p, :attrs nil,
:content ("world")})
user=> (apply str (emit* *1))
"<h2>hello</h2><p>world</p>"
user=> (deftemplate tmpl src [data] [#{:h2 :p}] nil [:h1] (after (map
h+p data)))
#'user/tmpl
user=> (apply str (tmpl [{:title "title#1" :text "paragraph#1"}
{:title "title#2" :text "paragraph#2"}]))
"<html><body><h1>Big
Title</h1><h2>title#1</h2><p>pragraph#1</p><h2>title#2</h2><p>pragraph#2</p><div
class=\"footer\">a footer</div></body></html>"

> How would one loop over something like this using Enlive snippets +
> templates? If there was a container element it would be simple, but
> without it I don't see anything obvious. Is there a way to support
> this case easily?

If there was a container element, one could also clone it and then
strip it from the result to leave only the repeated inner nodes.

In order to support (just musing not promising anything) such cases,
how would one define a group of adjacent nodes?

Christophe

David Nolen

unread,
Feb 2, 2010, 5:48:43 PM2/2/10
to Enlive
On Feb 1, 4:53 pm, Christophe Grand <christo...@cgrand.net> wrote:
>
> If there was a container element, one could also clone it and then
> strip it from the result to leave only the repeated inner nodes.

My issue with this is that we are now offloading the problem onto the
person that is writing the markup. Ideally Enlive can just support
common cases.

I like your solution using a snippet with the #{:h2 :p} selector:

But I was thinking it would be nice to combine this with my "model"
idea from the earlier mailing list post:

(def h+p (html/selector [:div#main :> #{[:h2 (html/nth-of-type 1)]
[:p (html/nth-of-type
1)]}]))

(html/defsnippet my-snippet source h+p
[{:keys [title text]}]
[:h2] (html/content title)
[:p] (html/content text))

This actually works pretty well from what I'm describing. And though
it's certainly more verbose than this:

{% for x in coll %}
<h>{{ x.heading }}</h>
<p>{{ x.content }}</p>
{% endfor %]

The Enlive version is composable and the above is not (beyond copy and
paste).

>
> In order to support (just musing not promising anything) such cases,
> how would one define a group of adjacent nodes?
>
> Christophe

As for making it more elegant and less verbose what about something
like the following?

(html/defsnippet name src selector
[ctxt]
[:h2] :and [:p] (fn [h2 p] (...)))

I like this because preserve the idea of the selector/fn pair. If both
selectors aren't matched we just fail and move on to the next
selector/
fn pair.

David

Christophe Grand

unread,
Feb 3, 2010, 1:32:38 AM2/3/10
to enliv...@googlegroups.com
Hello,

On Tue, Feb 2, 2010 at 3:47 AM, David Nolen <dnolen...@gmail.com> wrote:
> My issue with this is that we are now offloading the problem onto the
> person that is writing the markup. Ideally Enlive can just support
> common cases.

I agree.


> But I was thinking it would be nice to combine this with my "model"
> idea from the earlier mailing list post:
>
> (def h+p (html/selector [:div#main :> #{[:h2 (html/nth-of-type 1)]
> [:p (html/nth-of-type
> 1)]}]))
>
> (html/defsnippet my-snippet source h+p
> [{:keys [title text]}]
> [:h2] (html/content title)
> [:p] (html/content text))

This is nice too but still unsatisfactory.


>> In order to support (just musing not promising anything) such cases,
>> how would one define a group of adjacent nodes?
>

> As for making it more elegant and less verbose what about something
> like the following?
>
> (html/defsnippet name src selector
> [ctxt]
> [:h2] :and [:p] (fn [h2 p] (...)))

It's close to what I have in mind but I think there's more value in
being able to specify an "extent" between two selectors:

(deftemplate name src [data]
(extent [:h2] [:p]) (clone-for [{:keys [title text]}]
[:h2] (content title)
[:p] (content text)))

An extent (if antyone has a better name please chime in) being all the
nodes between two siblings, the leftmost being selected by the first
selector, the rightmost being selected by the second selector.

What do you think?

> I like this because preserve the idea of the selector/fn pair. If both
> selectors aren't matched we just fail and move on to the next selector/
> fn pair.

What I propose is to have an new class of selectors which don't select
a single node but several *adjacent* nodes (including whitespaces and
comments) at once.
The fn would, in this case, take a seq of nodes.

Christophe

--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.cgrand.net/ (en)

David Nolen

unread,
Feb 3, 2010, 2:07:37 AM2/3/10
to enliv...@googlegroups.com
On Wed, Feb 3, 2010 at 1:32 AM, Christophe Grand <chris...@cgrand.net> wrote:


(deftemplate name src [data]
 (extent [:h2] [:p]) (clone-for [{:keys [title text]}]
                      [:h2] (content title)
                      [:p] (content text)))

An extent (if antyone has a better name please chime in) being all the
nodes between two siblings, the leftmost being selected by the first
selector, the rightmost being selected by the second selector.

What do you think?

Something like extent would be really fantastic!

Chas Emerick

unread,
Feb 3, 2010, 6:54:39 AM2/3/10
to enliv...@googlegroups.com

On Feb 3, 2010, at 1:32 AM, Christophe Grand wrote:

> It's close to what I have in mind but I think there's more value in
> being able to specify an "extent" between two selectors:
>
> (deftemplate name src [data]
> (extent [:h2] [:p]) (clone-for [{:keys [title text]}]
> [:h2] (content title)
> [:p] (content text)))
>
> An extent (if antyone has a better name please chime in) being all the
> nodes between two siblings, the leftmost being selected by the first
> selector, the rightmost being selected by the second selector.
>
> What do you think?

Regarding terminology, I'd call it a 'slice', which I think fits in
with the slice operation that many people are familiar with in python,
etc.

However...

On Tue, Feb 2, 2010 at 3:47 AM, David Nolen <dnolen...@gmail.com>
wrote:
> My issue with this is that we are now offloading the problem onto the
> person that is writing the markup. Ideally Enlive can just support
> common cases.

Just out of curiosity, is this really a common case? In my
(admittedly limited) experience, if one is looking to emit repeated
sequences of elements as described, then the CSS is going to almost
certainly need some container around each such sequence anyway. Is
this less universal than I suspect?

- Chas

David Nolen

unread,
Feb 3, 2010, 11:18:25 AM2/3/10
to enliv...@googlegroups.com
On Wed, Feb 3, 2010 at 6:54 AM, Chas Emerick <ceme...@snowtide.com> wrote:
Regarding terminology, I'd call it a 'slice', which I think fits in with the slice operation that many people are familiar with in python, etc.


'extent' and 'slice' are both cool with me.
 
However...

Just out of curiosity, is this really a common case?  In my (admittedly limited) experience, if one is looking to emit repeated sequences of elements as described, then the CSS is going to almost certainly need some container around each such sequence anyway.  Is this less universal than I suspect?

You don't need a container to style repeating elements with CSS. I've seen this pattern a lot in PHP as well as in Django templates. I've been handed markup that looks this from designers as well. Many JavaScript accordions expect this pattern.


David

Chas Emerick

unread,
Feb 3, 2010, 1:05:06 PM2/3/10
to enliv...@googlegroups.com

On Feb 3, 2010, at 11:18 AM, David Nolen wrote:

Just out of curiosity, is this really a common case?  In my (admittedly limited) experience, if one is looking to emit repeated sequences of elements as described, then the CSS is going to almost certainly need some container around each such sequence anyway.  Is this less universal than I suspect?

You don't need a container to style repeating elements with CSS. I've seen this pattern a lot in PHP as well as in Django templates. I've been handed markup that looks this from designers as well. Many JavaScript accordions expect this pattern.


Thanks very much for the clarification and reference.

- Chas

Christophe Grand

unread,
Feb 4, 2010, 2:21:06 PM2/4/10
to enliv...@googlegroups.com
What about {[:h1] [:p]} to denote the extent/slice selector?

In this html snippet: <div><h1>title</h1><p>p1</p><p>p2</p></div> do
you think that {[:h1] [:p]} should select:
* <h1>title</h1><p>p1</p>
* <h1>title</h1><p>p1</p><p>p2</p>
* both

Right now my vote is on the first choice as it seems the least surprising.

> --
> 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.
>

--

David Nolen

unread,
Feb 4, 2010, 2:42:01 PM2/4/10
to enliv...@googlegroups.com
On Thu, Feb 4, 2010 at 2:21 PM, Christophe Grand <chris...@cgrand.net> wrote:
What about {[:h1] [:p]} to denote the extent/slice selector?

In this html snippet: <div><h1>title</h1><p>p1</p><p>p2</p></div> do
you think that  {[:h1] [:p]} should select:
* <h1>title</h1><p>p1</p>
* <h1>title</h1><p>p1</p><p>p2</p>
* both

Right now my vote is on the first choice as it seems the least surprising.

What about a greedy option?

{[:h1] [:p]} defaults to not greedy

{[:h1] [:p] :greedy true}

Or something more elegant to describe the greedy option?

David

Christophe Grand

unread,
Feb 4, 2010, 3:33:07 PM2/4/10
to enliv...@googlegroups.com

It's a possibility but I'd like to pick a single option for the moment
and see if the need for the other arises.
Note that {[:h1] [:p] :greedy true} can be written {[:h1] [[last-of-type :p]]}

So, do you have a strong opinion on which option to choose?

Christophe

David Nolen

unread,
Feb 4, 2010, 3:42:31 PM2/4/10
to enliv...@googlegroups.com
On Thu, Feb 4, 2010 at 3:33 PM, Christophe Grand <chris...@cgrand.net> wrote:
It's a possibility but I'd like to pick a single option for the moment
and see if the need for the other arises.
Note that {[:h1] [:p] :greedy true} can be written {[:h1] [[last-of-type :p]]}

Great, works for me.
 
So, do you have a strong opinion on which option to choose?

+1 for the 1st option.

David

Christophe Grand

unread,
Feb 5, 2010, 2:39:52 PM2/5/10
to enliv...@googlegroups.com
It occurred to me there already exists a commonly used term for what
{[:h1] [:p]} denotes: a fragment!

I created a kind of todo list: http://wiki.github.com/cgrand/enlive/whats-next

Reply all
Reply to author
Forward
0 new messages