-
-
Save getify/3696453 to your computer and use it in GitHub Desktop.
{ | |
"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 |
thanks @mmchaney i made some comments/questions on your gist.
OK I'll add that example too.
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):
- how do i loop over some data (like object properties)?
- 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?
- in the inner loop, how do i reference a value from the outer loop's iteration (like it's key/index, etc)?
- 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.
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.
@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.
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.
This exercise has made me like doT even more than I did already :-)
@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.
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}
...next time I'll set up my own gist so I can edit my typos...
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.)
a few comments:
-
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.
-
If the
options
array that you loop over had a property calledname
, 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?
So, for a nested loop, would I do this?
{{~ collection :item :index }}
... blah ...
{{~ collection :item2 :index2 }}
outer item: {{! item }} and inner item: {{~ item2 }}
{{~}}
... blah ...
{{~}}
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 :-)
@Pointy what happens if you accidentally name the inner loop variables the same as the outer loop variables?
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".
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}}
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}}
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
thanks @BorisMoore!
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
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
Thanks so much Patrick! Very much appreciate the input for Raptor. :)
thanks @Pointy. added some feedback there on your gist.