If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
You know what's always kind of a bummer... in any language? Creating strings that have variables inside. I know, that doesn't sound like that big of a deal, but it kind of is... especially in JavaScript!
For example, let's say that our favorite food is, of course, gelato:
const favoriteFood = 'gelato'; | |
... lines 2 - 5 |
And below that, we want to create a string that talks about this: The year is, end quote,
plus, and then new Date().getFullYear()
, another plus, open quote, space, my favorite
food is, another quote, one more plus, and favoriteFood
:
const favoriteFood = 'gelato'; | |
const iLoveFood = 'The year is '+(new Date()).getFullYear()+' and my favorite food is '+favoriteFood; | |
... lines 3 - 5 |
Perfectly straightforward and perfectly ugly! At the bottom, log that:
const favoriteFood = 'gelato'; | |
const iLoveFood = 'The year is '+(new Date()).getFullYear()+' and my favorite food is '+favoriteFood; | |
console.log(iLoveFood); |
Now, go enjoy our finished work.
This is the way we used to do things in JavaScript. But no more! Thanks to ES2015,
we now have something called Template Strings. And it's awesome. Instead of quotes,
use a tick. And as soon as you do that, you're allowed to put variables inside
of your string! Remove this single quote plus garbage. Replace it with ${
, the
variable name, then }
. Then, remove all the ugliness!
const favoriteFood = 'gelato'; | |
const iLoveFood = `The year is ${(new Date()).getFullYear()} and my favorite food is ${favoriteFood}`; | |
console.log(iLoveFood); |
Usually, you'll use this to print a variable, but you can use any expression you want: like we just did with the date.
Try it out! Ah, the same, wonderful string.
There's something else that makes strings especially annoying in JavaScript: line breaks. In the first episode, we created a client side template and used Underscore.js to render it. We decided to put the template itself into Twig:
... lines 1 - 53 | |
{% block javascripts %} | |
... lines 55 - 66 | |
<script type="text/template" id="js-rep-log-row-template"> | |
</script> | |
{% endblock %} |
Then, in _addRow()
, we found that script
element by its id and fetched the string:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
class RepLogApp { | |
... lines 5 - 166 | |
_addRow(repLog) { | |
... lines 168 - 171 | |
const tplText = $('#js-rep-log-row-template').html(); | |
const tpl = _.template(tplText); | |
const html = tpl(repLog); | |
this.$wrapper.find('tbody').append($.parseHTML(html)); | |
this.updateTotalWeightLifted(); | |
} | |
} | |
... lines 181 - 216 | |
})(window, jQuery, Routing, swal); |
So, why did we put the the template in Twig? Well, it's not a bad option, but mostly, we did it because putting that template into JavaScript was, basically, impossible.
Why? Try it! Copy the template, find the bottom of RepLogApp.js
, create
a new const rowTemplate
and paste the string inside single quotes:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 215 | |
const rowTemplate = ' | |
<tr data-weight="<%= totalWeightLifted %>"> | |
<td><%= itemLabel %></td> | |
<td><%= reps %></td> | |
<td><%= totalWeightLifted %></td> | |
<td> | |
<a href="#" | |
class="js-delete-rep-log" | |
data-url="<%= links._self %>" | |
> | |
<span class="fa fa-trash"></span> | |
</a> | |
</td> | |
</tr> | |
'; | |
... lines 231 - 232 | |
})(window, jQuery, Routing, swal); |
Oh wow, PHP storm is SO angry with me. Furious! And that's because, unlike PHP, you are not allowed to have line breaks inside traditional strings. And that makes putting a template inside of JavaScript basically impossible.
Guess what? Template strings fix that! Just change those quotes to ticks, and everything is happy:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 216 | |
const rowTemplate = ` | |
<tr data-weight="<%= totalWeightLifted %>"> | |
<td><%= itemLabel %></td> | |
<td><%= reps %></td> | |
<td><%= totalWeightLifted %></td> | |
<td> | |
<a href="#" | |
class="js-delete-rep-log" | |
data-url="<%= links._self %>" | |
> | |
<span class="fa fa-trash"></span> | |
</a> | |
</td> | |
</tr> | |
`; | |
... lines 232 - 233 | |
})(window, jQuery, Routing, swal); |
To celebrate, remove the template from Twig:
... lines 1 - 53 | |
{% block javascripts %} | |
{{ parent() }} | |
<script src="https://cdn.jsdelivr.net/sweetalert2/6.1.0/sweetalert2.min.js"></script> | |
<script src="{{ asset('assets/js/RepLogApp.js') }}"></script> | |
<script> | |
$(document).ready(function() { | |
var $wrapper = $('.js-rep-log-table'); | |
var repLogApp = new RepLogApp($wrapper); | |
}); | |
</script> | |
{% endblock %} |
Now, we can simplify things! Up in _addRow()
, set tplText
to simply equal rowTemplate
:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
class RepLogApp { | |
... lines 6 - 167 | |
_addRow(repLog) { | |
... lines 169 - 172 | |
const tplText = rowTemplate; | |
... lines 174 - 179 | |
} | |
} | |
... lines 182 - 216 | |
const rowTemplate = ` | |
<tr data-weight="<%= totalWeightLifted %>"> | |
<td><%= itemLabel %></td> | |
<td><%= reps %></td> | |
<td><%= totalWeightLifted %></td> | |
<td> | |
<a href="#" | |
class="js-delete-rep-log" | |
data-url="<%= links._self %>" | |
> | |
<span class="fa fa-trash"></span> | |
</a> | |
</td> | |
</tr> | |
`; | |
window.RepLogApp = RepLogApp; | |
})(window, jQuery, Routing, swal); |
But let's go a step further! Now that we have this nice ability to embed variables into our strings, could we create our own templating engine, instead of using the one from Underscore? I mean, there's nothing wrong with Underscore, but we can accomplish this with pure template strings.
First, update the template with ${repLog.totalWeightLifted}
. In a moment, instead
of making the individual variables available, we'll just set one variable: the
repLog
object:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 216 | |
const rowTemplate = ` | |
<tr data-weight="${repLog.totalWeightLifted}"> | |
... lines 219 - 229 | |
</tr> | |
`; | |
... lines 232 - 233 | |
})(window, jQuery, Routing, swal); |
Of course, PhpStorm is angry because there is no repLog
variable... but we can
fix that!
Next, make the same change very carefully to the other variables: replacing the kind of ugly syntax with the template string syntax. And yea, be more careful than I am: I keep adding extra characters!
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 216 | |
const rowTemplate = ` | |
<tr data-weight="${repLog.totalWeightLifted}"> | |
<td>${repLog.itemLabel}</td> | |
<td>${repLog.reps}</td> | |
<td>${repLog.totalWeightLifted}</td> | |
<td> | |
<a href="#" | |
class="js-delete-rep-log" | |
data-url="${repLog.links._self}" | |
> | |
<span class="fa fa-trash"></span> | |
</a> | |
</td> | |
</tr> | |
`; | |
... lines 232 - 233 | |
})(window, jQuery, Routing, swal); |
At this point, this is a valid template string... with one screaming problem: there
is no repLog
variable! If we refresh now, we of course see:
repLog
is not defined
Here's the goal: inside of _addRow()
, we need to somehow use that template string,
and pass it the repLog
variable we have here. But... how can we make this variable
available... way down here?
Hey! Here's an idea: let's wrap this in a function! Instead of rowTemplate
simply
being a string, set it to an arrow function with a repLog
argument:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 213 | |
const rowTemplate = (repLog) => ` | |
<tr data-weight="${repLog.totalWeightLifted}"> | |
<td>${repLog.itemLabel}</td> | |
<td>${repLog.reps}</td> | |
<td>${repLog.totalWeightLifted}</td> | |
<td> | |
<a href="#" | |
class="js-delete-rep-log" | |
data-url="${repLog.links._self}" | |
> | |
<span class="fa fa-trash"></span> | |
</a> | |
</td> | |
</tr> | |
`; | |
... lines 229 - 230 | |
})(window, jQuery, Routing, swal); |
And suddenly, the template string will have access to a repLog
variable.
Back in _addRow()
, remove all this stuff and very simply say
html = rowTemplate(repLog)
:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
class RepLogApp { | |
... lines 6 - 167 | |
_addRow(repLog) { | |
... lines 169 - 172 | |
const html = rowTemplate(repLog); | |
this.$wrapper.find('tbody').append($.parseHTML(html)); | |
this.updateTotalWeightLifted(); | |
} | |
} | |
... lines 179 - 230 | |
})(window, jQuery, Routing, swal); |
And that is it! Try that out! Refresh! The fact that it even loaded proves it works: all of these rows are built from that template.
Before we move on, there's one other strange thing you might see with template strings.
If you downloaded the start code for this project, you should have a tutorial
directory
with an upper.js
file inside. Copy that function. And then, at the bottom of
RepLogApp.js
, paste it before the template:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 213 | |
function upper(template, ...expressions) { | |
return template.reduce((accumulator, part, i) => { | |
return accumulator + (expressions[i - 1].toUpperCase ? expressions[i - 1].toUpperCase() : expressions[i - 1]) + part | |
}) | |
} | |
... lines 219 - 236 | |
})(window, jQuery, Routing, swal); |
Just follow me on this: right before the tick that starts the template string, add the
function's name: upper
:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 213 | |
function upper(template, ...expressions) { | |
return template.reduce((accumulator, part, i) => { | |
return accumulator + (expressions[i - 1].toUpperCase ? expressions[i - 1].toUpperCase() : expressions[i - 1]) + part | |
}) | |
} | |
const rowTemplate = (repLog) => upper` | |
... lines 221 - 233 | |
`; | |
... lines 235 - 236 | |
})(window, jQuery, Routing, swal); |
So literally upper
, then without any spaces, the opening tick. This is called
a tagged template. And by doing this, the string and its embedded expressions will
be passed into that function, allowing it to do some transformations. In this case,
it'll upper case all the words.
Other possible uses for tagged templates might be to escape variables. But honestly, the web seems to be filled mostly with possible use-cases, without too many real-world examples. And also, these functions are really complex to write and even harder to read!
Oh, btw, the upper
function uses the spread operator in a different way: to allow
the function to have a variable number of arguments!
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 213 | |
function upper(template, ...expressions) { | |
... lines 215 - 217 | |
} | |
... lines 219 - 236 | |
})(window, jQuery, Routing, swal); |
Anyways, let's see if it works! Refresh! Hey, all uppercase! Ok, kinda cool! You may not use tagged template strings, but you might see them. And now, you'll understand what the heck is going on!
Remove the upper
function and put everything back to normal:
... lines 1 - 2 | |
(function(window, $, Routing, swal) { | |
... lines 4 - 213 | |
const rowTemplate = (repLog) => ` | |
... lines 215 - 227 | |
`; | |
... lines 229 - 230 | |
})(window, jQuery, Routing, swal); |
Ohh, that was totally a mistake, thanks Agnes for letting us know!
We really apologize if caused you any problem, it's already fixed, you can download the project and just copy it into your working project :)
If you have more questions or comments let us know.
Cheers!
// composer.json
{
"require": {
"php": "^7.2.0",
"symfony/symfony": "3.2.*", // v3.2.14
"twig/twig": "2.10.*", // v2.10.0
"doctrine/orm": "^2.5", // v2.7.1
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
"symfony/swiftmailer-bundle": "^2.3", // v2.4.2
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.3.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
"doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.2
"symfony/phpunit-bridge": "^3.0" // v3.2.2
}
}
Hmm, no tutorial directory in the code.