Skip to content

Instantly share code, notes, and snippets.

@getify
Created September 11, 2012 06:21
Show Gist options
  • Save getify/3696453 to your computer and use it in GitHub Desktop.
Save getify/3696453 to your computer and use it in GitHub Desktop.
how does your templating approach handle this task?
{
"settings" : {
"foo" : "low",
"bar" : "high",
"baz" : "low"
}
}
<!--
NOTE: see this comment for clarification of why this
scenario is structured this way intentionally:
https://gist.github.com/3696453#gistcomment-570903
Scenario:
Given the data structure above in "1.json", how would you
use a templating engine to generate this snippet of html
as a string? NOTE: the spirit of this question is to
generate a string of markup with a template engine, NOT
to do DOM manipulation. Imagine needing to generate this
kind of markup server-side to send over the wire.
-->
<h1>Settings</h1>
<h2>foo</h2>
<input type="radio" name="foo" value="low" checked> low
<input type="radio" name="foo" value="high"> high
<h2>bar</h2>
<input type="radio" name="bar" value="low"> low
<input type="radio" name="bar" value="high" checked> high
<h2>baz</h2>
<input type="radio" name="baz" value="low" checked> low
<input type="radio" name="baz" value="high"> high
here's how "grips" template engine would handle this scenario:
https://gist.github.com/3706912
@getify
Copy link
Author

getify commented Sep 12, 2012

@Pointy -- could you provide a gist showing how you'd do the snippet above? very curious in my research to compare apples-to-apples on tasks like this.

@nathanpalmer
Copy link

jQuery Templates: https://gist.github.com/3707126 (yes I know the project is dead)

@getify
Copy link
Author

getify commented Sep 12, 2012

thanks @nathanpalmer!

@Pointy
Copy link

Pointy commented Sep 12, 2012

Here's my gist. Let me know if I can clarify; it's pretty simple however, just an encapsulation of the decision-making involved with the tag attributes.

@mmchaney
Copy link

My handlebars solution with two helpers: https://gist.github.com/3707466

Template markup could maybe be slimmed down by introducing another helper for iterating over array literals.

@getify
Copy link
Author

getify commented Sep 12, 2012

thanks @Pointy. added some feedback there on your gist.

@getify
Copy link
Author

getify commented Sep 12, 2012

thanks @mmchaney i made some comments/questions on your gist.

@Pointy
Copy link

Pointy commented Sep 12, 2012

OK I'll add that example too.

@getify
Copy link
Author

getify commented Sep 12, 2012

FTA: my scenario above intentionally is looking to see how templating engine solutions handle the following "complications" mixed together (rather than individually, as most samples/tutorials do):

  1. how do i loop over some data (like object properties)?
  2. how do i do an inner loop over either some more data, or (in this case), a fixed set (an array literal, if you will) of values?
  3. in the inner loop, how do i reference a value from the outer loop's iteration (like it's key/index, etc)?
  4. in the inner loop, how do i make a compute a conditional decision, and depending on that, include some text (or a sub-template) or not.

@Pointy
Copy link

Pointy commented Sep 12, 2012

One thing I'll note is that I think that structure is potentially problematic. There's no firm guarantee that JavaScript will iterate through the object properties in their declared order. Maybe that's OK in your example, but if it were my application it'd be an array of name/value pair objects.

@getify
Copy link
Author

getify commented Sep 12, 2012

@Pointy -- fair point. I don't think the spirit of my question would be much different if we were iterating over an ordered array vs. properties in an object.

btw, even though iteration order is not explicitly defined by ES, it's de facto agreed on as insertion order across all the major JS engines. it's rather predictable.

nevertheless, your point about reliable ordering stands.

@Pointy
Copy link

Pointy commented Sep 12, 2012

OK yes I see what you're getting at. Well, I think the reason I didn't see the complexity is that I'm really only familiar with working with doT templates. That tool creates a JavaScript function for each template, and all the doT primitives translate into simple JavaScript code. Thus, there aren't any scope issues - iteration variables in nested loops are explicitly named, and those names are directly used as JavaScript variable names. Thus, just as you'd nest for loops in JavaScript with "i" and "j" iterator variables, you'd do the same thing in doT.

@Pointy
Copy link

Pointy commented Sep 12, 2012

This exercise has made me like doT even more than I did already :-)

@getify
Copy link
Author

getify commented Sep 12, 2012

@Pointy most templating engines we're considering here ALSO compile the template down to javascript. So that's not really the issue.

The issue is, in naked javascript, you can manually choose to name your iterator i in the outer loop and j in the inner loop, but in templating engines, they generally have a generic variable in scope for each loop iteration, like index or key or something, or even just a . to refer to the current iteration context. But in all these cases, it's awkward from inside the inner loop to refer to the scope of the outer loop, since the inner loop's variables, whatever they are called, shadowed/overwrote the outer loop.

Does "doT" have some magical behavior where it names inner loop variables differently than the outer loop? How does that work?

Moreover, if it doesn't have that logic, and the inner loop isn't inside it's own function enclosure, then it can definitely happen that you accidentally break the outer loop by changing an inner loop variable (of the same name as the outer loop's iterator), right?

In "grips", for each loop I handle, I create a nested (function(){ ... })(..) enclosure, so that my loops can't interfere with anything outside them, including an outer loop that may be wrapped around it.

@simonblee
Copy link

You can't iterate over objects in Dust (without a helper) so you can just massage the data into arrays of objects like. If you use LinkedIn's fork then you can use their custom @if and @eq helpers (I won't to keep the rending fast). This is not exactly what you asked for but I'd also like to keep the template generic without 'high' and 'low' options and then only change the data. Then you can reuse it:

{
    "settings" : [
        {name : "foo", options : [{ val : "low", check : true}, { val : "high" }] },
        {name : "bar", options : [{ val : "low" }, { val : "high", check : true }] },
        {name : "baz", options : [{ val : "low", check : true }, { val : "high" }] }
    ]
}

Then use the following template (to keep it more generic, I'd use a partial for the {#settings} array in a real project):

<h1>Settings</h1>
{#settings}
        <h2>{name}</h2>
        {#options}<input type="radio" name="{name}" value="{val}" {?check}checked{/check}>{val}{/options}
{/settings}

@simonblee
Copy link

...next time I'll set up my own gist so I can edit my typos...

@Pointy
Copy link

Pointy commented Sep 12, 2012

Yes, @getify, in doT a loop looks like this:

{{~ collection :item :index }}
  ... template content ...
{{~}}

Inside the loop, "item" and "index" are bound to the array element and its index, respectively. (Loops like that only work on arrays, but that's not a problem because it's so easy to transform an object into an array of name/value pairs.)

@getify
Copy link
Author

getify commented Sep 12, 2012

@simonblee

a few comments:

  1. Imagine if there were 50, 100, 1000 items in the list, and each had 10 possible values (imagine a big survey page grid of radio buttons, for instance). you're talking about creating a huge "sparse" data structure of a bunch of repeated values, so you can specify the "columns" over again for each "row". doesn't that seem crazy inefficient?

    I understand you prefer the idea of being generic, but in some cases, like for a survey, there's a fixed set (or range) of values for the "columns", and it's the UI designer who controls that in the template, not the back-end data layer. Asking the UI designer to create a complex data transform, AND have a whole bunch of extra data in memory, seems unnecessary to me. This is one of those cases where I think the template engine should assist.

  2. If the options array that you loop over had a property called name, would that shadow/override the outer loop's {name} so that you couldn't easily reference it inside the inner loop? How does that sort of variable scoping complication get handled in Dust?

@getify
Copy link
Author

getify commented Sep 12, 2012

@Pointy

So, for a nested loop, would I do this?

{{~ collection :item :index }}
  ... blah ...
  {{~ collection :item2 :index2 }}
     outer item: {{! item }}  and inner item: {{~ item2 }}
  {{~}}
  ... blah ...
{{~}}

@Pointy
Copy link

Pointy commented Sep 12, 2012

Yes, exactly. It seems very natural to me because I'm coming from a JSP background, and that's pretty much exactly how iterating looks in JSP (except of course it's 1000% uglier in JSP :-)

@getify
Copy link
Author

getify commented Sep 12, 2012

@Pointy what happens if you accidentally name the inner loop variables the same as the outer loop variables?

@Pointy
Copy link

Pointy commented Sep 12, 2012

Then the template doesn't work. I try to avoid that :-) I have a hard time thinking of a time that happened to me in JSP (or JavaScript for that matter) any time recently (or not-so-recently even). Generally I use pretty good names (not huge javaProgrammerApproved names but good ones), so that I can stay sane, so unless I've got a list of things with the same sort of thing inside it's pretty easy to keep the names straight. Heck even with a nested loop for a table, I can at least use "row" and "col".

@pasaran
Copy link

pasaran commented Sep 13, 2012

@fearphage
Copy link

doT.js

{{##def.radiogroup:
<h2>{{! name}}</h2>
{{~['low', 'high'] :value}}
<input type="radio" name="{{! name }}" value="{{= value }}"{{? it.settings[name] == value }} checked="checked"{{?}}/>
{{~}}
#}}

{{##def.settings:
<h1>Settings</h1>
{{~Object.keys(it.settings) :name}}
{{#def.radiogroup}}
{{~}}
#}}

{{#def.settings}}

@fearphage
Copy link

doT.js (cleaned up a bit)

{{##def.radiogroup:
  <h2>{{= name}}</h2>
  {{~['low', 'high'] :value}}
    <input type="radio" name="{{= name }}" value="{{= value }}"{{? it.settings[name] == value }} checked="checked"{{?}}/>
  {{~}}
#}}

{{##def.settings:
  <h1>Settings</h1>
  {{~Object.keys(it.settings) :name}}
    {{#def.radiogroup}}
  {{~}}
#}}

{{#def.settings}}

@BorisMoore
Copy link

BorisMoore commented Sep 15, 2012

Here's one approach you can use with JsRender: https://gist.github.com/3730412

UPDATE: The gist has been deleted. It is very easy to do in JsRender using the {{props}} tag

@getify
Copy link
Author

getify commented Sep 16, 2012

thanks @BorisMoore!

@patrick-steele-idem
Copy link

As requested, here's the solution for Raptor Templates. The below template will produce the exact output (less extra whitespace):

<c:template xmlns:c="core" params="settings">

    <h1>Settings</h1>
    <c:for each="(name,value) in settings">
        <h2>$name</h2>
        <c:for each="option in ['low', 'high']">
            <input type="radio" 
                name="$name" 
                value="$option" 
                checked="${value === option ? null : undefined}" /> 
            $option
        </c:for>
    </c:for>

</c:template>

Admittedly, it is a little odd to use "null" as the value for attributes that do not have a value in the resulting HTML, but it works. However, I prefer to to output checked="checked" at the expense of a few extra bytes since it's cleaner in my implementation and it still produces valid HTML:

<c:template xmlns:c="core" params="settings">

    <h1>Settings</h1>
    <c:for each="(name,value) in settings">
        <h2>$name</h2>
        <c:for each="option in ['low', 'high']">
            <input type="radio" 
                name="$name" 
                value="$option" 
                checked="{?value === option;checked}" /> 
            $option
        </c:for>
    </c:for>

</c:template>

You can easily try out the templates online at http://raptorjs.org/raptor-templates/try-online/

Let me know what you think.

Thanks,
Patrick

@patrick-steele-idem
Copy link

For reference, I created a Gist that shows the input Raptor template, the generated HTML and the compiled template:
https://gist.github.com/3966647

--Patrick

@getify
Copy link
Author

getify commented Oct 28, 2012

Thanks so much Patrick! Very much appreciate the input for Raptor. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment