- Explain how to use lodash templates
- Practice using lodash templates to organize JavaScript interactions
- Trigger changes in the DOM in response to user actions
Dynamically adding and manipulating chunks of the DOM based on data can be a tedious, error-prone activity when we try to manually create HTML strings out of data, like this:
var commentNode = document.getElementById("comments");
commentNode.innerHTML = "<div class='some-class'>This is a comment and
it's real long</div><p>" + someDynamicValue + "</p><p>" + someOtherValue
+ "</p>";
The better way is to use a template that provides a way to use dynamically add data on a set HTML structure, preventing us from having to type out these monster strings of data and markup.
We could build out our own template for each section of our blog, creating a new function for each part, and that would work, but there are tools out there to make this task nice and easy for us.
Lodash is a JavaScript library that provides a lot of utilities for working with arrays, objects, and strings, including a template library which is what we'll be working with today.
Let's look at the earlier example:
commentNode.innerHTML = "<div class='some-class'>This is a comment and
it's real long</div><p>" + someDynamicValue + "</p><p>" + someOtherValue
+ "</p>"
The <div class='some-class'>...
string is our template. It's not very
pretty, but it's ours. We load data into the template by closing the
string and concatenating variables then continuing the template string.
If you're anything like me, you've forgotten to type at least one of the
plus signs nearly every time you've done any long string concatenation,
so, bare minimum, our templating tool should fix this problem.
Let's wire up a basic comments form to our blog post. Starting with the
markup in index.html
, we'll add a simple form, and a placeholder for
the comments to be displayed.
<article>
<header><h2>The Results are In!</h2></header>
<p>
After careful consideration and a generous grant from NASA, our team has determined that a woodchuck could chuck five wood, with a standard deviation of +/- .37 wood.
</p>
<footer>posted by Chuck Wooden</footer>
<form onsubmit="postComment();return false;">
Name: <input type="text" id="commenterName"><br>
Comment: <input type="textarea" id="commentText"><br>
<input type="submit">
</form>
<div id="comments"></div>
</article>
So we have a basic form that will submit with a postComment()
function. Let's add the basic bits of that to our index.js
.
function postComment() {
var commenter = document.getElementById("commenterName").value;
var comment = document.getElementById("commentText").value;
//insert comment into "comments" div in this format:
//<div class="comment"><p>comment</p><p>Posted By: <span class="commenter">commenter</span></p></div>
}
We know the format of the template we want, but instead of adding it
using string concatenation, we're going to use the lodash template
function.
Top-tip: By default, we access lodash functions through the
underscore, which is a variable that holds the root object, like _.template()
. This is similar to accessing
jQuery functions via the $
variable. This is because lodash was
originally a fork of the Underscore.js library.
We'll start by building a template string as we have in the past, but instead of concatenation, we'll be using interpolation, which is a way to replace text in strings using special delimeters.
In lodash, we can interpolate strings with the <%= %>
delimeter, so let's try it out.
function postComment() {
var commenter = document.getElementById("commenterName").value;
var comment = document.getElementById("commentText").value;
//insert comment into "comments" div in this format:
//<div class="comment"><p>comment</p><p>Posted By: <span class="commenter">commenter</span></p></div>
var commentTemplate = '<div class="comment"><p><%= comment %></p><p>Posted By: <span class="commenter"><%= commenter %></span></p></div>';
}
We're creating a new variable called commentTemplate
that will hold our template string. We've wrapped our
two variables inside the interpolation delimiters, with <%= comment %>
and <%= commenter %>
.
The next step is to use the template()
function of lodash to turn this
template string into a function that we can call with a JSON argument
for the variable values.
function postComment() {
var commenter = document.getElementById("commenterName").value;
var comment = document.getElementById("commentText").value;
//insert comment into "comments" div in this format:
//<div class="comment"><p>comment</p><p>Posted By: <span class="commenter">commenter</span></p></div>
//create template string
var commentTemplate = '<div class="comment"><p><%= comment %></p><p>Posted By: <span class="commenter"><%= commenter %></span></p></div>';
//create template function
var templateFn = _.template(commentTemplate);
}
We create a function here by executing _.template()
, but we don't
execute it now. Why?
One of the reasons to use templating is to be able to reuse the templates. By creating a function and holding it in a variable, we can execute the function over and over with different data, rather than having to rebuild the template function every time.
Now that we have our function, we'll need to execute it with our form
data as a regular JSON object: { 'comment': value, 'commenter': value }
. Note that the keys for the object match the variable names inside
our interpolation delimiters.
Executing the function will return a string, which we can then append to our comments div. Let's finish it out.
function postComment() {
var commenter = document.getElementById("commenterName").value;
var comment = document.getElementById("commentText").value;
//insert comment into "comments" div in this format:
//<div class="comment"><p>comment</p><p>Posted By: <span class="commenter">commenter</span></p></div>
//create template string
var commentTemplate = '<div class="comment"><p><%= comment %></p><p>Posted By: <span class="commenter"><%= commenter %></span></p></div>';
//create template function
var templateFn = _.template(commentTemplate);
var commentsDiv = document.getElementById("comments");
//execute template function with JSON object for the interpolated values
var templateHTML = templateFn({ 'comment': comment, 'commenter': commenter });
//append rather than replace!
commentsDiv.innerHTML += templateHTML;
}
Reload your index.html
and see it in action!
Okay, this is all fine and good, and we aren't concatenating strings anymore, but we're still building out a huge string for the template, and this is a pretty simple template. What happens when we have a lot of markup to work with?
Putting our templates in a string isn't great for a couple of reasons. We don't get the human-readable, indented structure of regular HTML. We don't get the syntax highlighting on the tags that our editor provides. Adding or changing HTML inside a string is difficult and prone to error. What we need is a way to use a regular chunk of HTML as our template.
Fortunately, lodash gives us a great way to do this. We can use a
special <script>
tag with the content-type of text/x-lodash-template
and embed HTML directly inside. Let's redo our comment template.
This time, we're going to put this in the bottom of our index.html
like it was any other JavaScript block.
<!-- index.html, place before end body tag -->
<body...>
<article>...</article>
<script id="comment-template" type="text/x-lodash-template">
<div class="comment">
<div class="comment-body"><%= comment %></div>
<div class="commenter">
<p>
<span class="posted-by">Posted By: </span>
<%= commenter %>
</p>
</div>
</div>
</script>
</body>
Inside that <script id="comment-template">
tag we just have mostly
regular HTML! The only difference is, since we're inside a script
tag,
we also can use the interpolation delimiters from lodash to mark where
our dynamic data is.
Okay, we've made a significant change here, so surely there's a lot of work require to wire this up, right?
// index.js
function postComment() {
var commenter = document.getElementById("commenterName").value;
var comment = document.getElementById("commentText").value;
//insert comment into "comments" div in this format:
//<div class="comment"><p>comment</p><p>Posted By: <span class="commenter">commenter</span></p></div>
//create template string - THIS IS THE ONLY LINE WE HAVE TO CHANGE
//var commentTemplate = '<div class="comment"><p><%= comment %></p><p>Posted By: <span class="commenter"><%= commenter %></span></p></div>';
var commentTemplate = document.getElementById("comment-template").innerHTML;
//create template function
var templateFn = _.template(commentTemplate);
var commentsDiv = document.getElementById("comments");
//execute template function with JSON object for the interpolated values
var templateHTML = templateFn({ 'comment': comment, 'commenter': commenter });
//append rather than replace!
commentsDiv.innerHTML += templateHTML;
}
Nope! We just had to change the one line that defines the source string
for the template. Remember that HTML is just a string, and we can read
the innerHTML
property of any element. Since we gave the script
tag
an id
, we know how to query it, read its contents, and use that as our
template string.
Let's reload and give it a shot. Works great, right?
Since lodash expects a string argument for template()
, we just fed it
a different template string with the same interpolated values and everything else works just fine. Master hackers over here.
Using this technique, you can build out one or more HTML templates
inside script tags, taking advantage of the ease of editing HTML in its
normal form, then take the innerHTML
of those script tags and use it
to build lodash templates. No more giant strings!
We've seen how to interpolate text into our templates, but what if we want to prevent HTML tags from being evaluated? Or execute some JavaScript code on an interpolated value?
Lodash gives us what we need here as well, allowing us to use different delimiters to interpolate different things.
Let's say we don't want to allow our commenters to use HTML markup in their
comments. Right now, if you tried posting a comment like
<p>hi</p><p>everyone</p><h1>this is my comment</h1>
, our form would
let you. But of course, like everything else, trolls ruin this and find
ways to break our blog style with their markup, so we want to change our
comment field to not execute HTML tags.
Luckily, it's as simple as changing the delimiter from interpolate (<%= %>
) to escape (<%- %>
). Let's try it:
<script id="comment-template" type="text/x-lodash-template">
<div class="comment">
<div class="comment-body"><%- comment %></div>
<div class="commenter">
<p>
<span class="posted-by">Posted By: </span>
<%- commenter %>
</p>
</div>
</div>
</script>
A quick refresh and try to post that same comment again, and we'll see that our HTML has been escaped and the tags display harmlessly on the page.
Let's say we want to execute some non-printing JavaScript code in our template. We
can use the <% %>
, or eval delimiter.
For instance, we want to display the commenter's name, but if they
accidentally left it blank, we want to display "Anonymous". We can use
the eval delimiter to execute an if...else
statement.
<div class="commenter">
<p>
<span class="posted-by">Posted By: </span>
<% if(commenter !== '') { %>
<%= commenter %>
<% } else { %>
Anonymous
<% } %>
</p>
</div>
Top-tip: The eval delimiter is for executing non-printing code, that
is, code that won't display a result directly on the page, such as
if...then
blocks or for
loops. If you wanted to display the results
of a function, say, Date.now()
on the page, you would use the regular
interpolation delimiter, like <%= Date.now() %>
.
In this lesson we learned how to use lodash and the template()
function to create templates and dynamically add templated data to our
DOM. We saw that we can use a script
tag to hold complex HTML
templates rather than building our templates inside string variables.
Finally, we learned how to use the different delimiters to display text, escape HTML, and execute code for more powerful templates.