Is it possible to somehow take a DOM element from a underscore template and use it as another template?
The idea is that my app needs to render a document that contains
I know I'm 3 years late to the conversation, but none of these answers applied at all to a situation I had in my latest project. And since the way I was originally thinking of doing this ended up working, I figured I should post it in case anyone else wants to actually nest underscore.js templates, as the title of this question states.
My requirements were basically:
1) If there are dynamic pieces in a template, they should be able to re-render themselves individually--without requiring that the entire template be re-rendered with the updated data.
2) Dynamic pieces shouldn't need to be splintered off into their own templates on the same level as the parent template; rather, there should be a sort of template hierarchy--a template declaring all the sub-templates required for itself.
The concept of nested templates in Underscore.js is the same as in PHP (for example). The following code is an example of "echoing PHP":
<?php echo '<?php echo $var_unknown_until_runtime; ?>'; ?>
While this nested syntax can get super convoluted and confusing, the idea is simple:
Echo code that will echo values later.
This applies in Underscore.js templates as well.
The code will look like this:
<script type="text/template" id="outer-tpl">
<%= value %>
<script type="text/template" id="inner-tpl">
<%= '<%= value %\>' %> <!-- NOTE how the inner '>' is escaped ('\>') -->
<%= "</script\>" %> <!-- NOTE same thing -->
</script>
Let's break this down a little bit. The line with the first NOTE
is our key line here. Normally when Underscore compiles this template, you would expect the value of value
to be printed in both the outer and inner templates. We did a few things to prevent this from happening:
1) Turn the nested underscore statement into a string.
2) Interpolate that string (
<%= "string here" %>
).3) Escape the closing
>
in the nested underscore statement (\>
). This prevents Underscore's regex from matching this with the opening tag from step 2 (<%=
). When this string is echoed, the escape character will be removed, leaving%>
, ready to be interpreted by Underscore on the next pass.
Doing this same thing in the second NOTE
in our example prevents the browser from ending the first script
tag. Nested script
tags technically aren't allowed, so the browser looks for the first sequence of characters: </script>
and then ends script execution. This stops the browser from recognizing this sequence.
For a more in-depth example, see this JSFiddle.
I don't endorse this as anywhere near the best way to do this sort of thing. It is, after all, very hacky. While @John z's answer breaks my first requirement, which wasn't an option for me, the other option of splintering off dynamic pieces in a template into their own templates may be easier to deal with. It's just less organized.
The location of all these nested script
tags in your DOM can get hard to keep track of (as the nested script
tags will get injected dynamically into the DOM when and wherever you insert the outer template). The risk of duplication is high, and can be annoying to maintain.
The difficulty of nesting templates increases drastically for each level of nesting (to nest a template in a nested template, you'd have to escape everything again, etc...). The fiddle includes an example of this.
You can pass the nested template as a variable in the template assignments to the main template, e.g.:
HTML:
<script type="text/template" id="sub_template">
<article>
<h1>id: <%= id %><h1>
</article>
</script>
<script type="text/template" id="main_template">
<% for (var i = 0; i < num; i++) { %>
<%= renderSub({id:i}) %>
<% } %>
</script>
JS:
var renderSub = _.template( $('#sub_template').remove().text() ),
renderMain = _.template( $('#main_template').remove().text() );
renderMain({num:5, renderSub:renderSub});
Instead of loading it all at once as one template and then trying to load a portion of it again later, separate them as multiple templates.
Have your main document-template and a summary-template. The initial load pulls in the document-template and then pulls into the summary-template and inserts it into the compiled DOM from the first template. Then you can reload the second template at any time. See the following steps:
1) Load initial template, shown below:
<script type="text/template" id="document-template">
<div id="document">
<h1><%= name %></h1>
<ul class="items">
<% _.each(items, function(item) { %>
<li><%= item %></li>
<% }); %>
</ul>
</div>
</script>
2) Once this is compiled into the DOM, load the second template via underscore, shown below:
<script type="text/template" id="summary-template">
<div id="summary">
<p>Total items: <%= totalitems %></p>
</div>
</script>
3) Call $("#document").append(summaryTemplate), or whatever variable is holding the compiled template.
4) Repeat steps 2 and 3 any time you need to reload the summary, except first remove the existing $("#summary") before appending again
You can use this same strategy for the items as well, just make sure you use the correct jQuery selectors/methods so that you're overwriting the existing divs in the correct order, as append will only work for adding something to the end of an element.
/////// TEMPLATES //////
var mainTemplate = "<ul> \
<% _.each(items, function(item) { %> \
<%= listItem({item:item}) %> \
<% }); %> \
<ul>";
var subTemplate = '<li><%=item %></li>';
/////// MODEL (our data) //////
var model = {
items : [1,2,3,4,5]
}
/////// COMPILE //////
// add the subTemplate to the model data, to be passed to the mainTemplate
model.listItem = _.template(subTemplate);
// Render main template to the DOM
document.body.innerHTML = _.template(mainTemplate, model);