You are on page 1of 82

JavaScript for PHP Geeks:

ES6/ES2015 (New JavaScript)

With <3 from SymfonyCasts


Chapter 1: ECMAScript 2015 / ES6 / Harmony / Cookies

Welcome back! Oh man am I super excited about this tutorial. In the first episode, we dug deep into JavaScript: its great
features, its weird features, and ultimately, enough good stuff to start getting our code organized.

But, uh, I ignored a gigantic thing in the JavaScript world. I mean, really big, like trying to stay cool when Godzilla is stomping
on cars all around you! JavaScript... has a new version... with a ton of new features and significant changes to the language!
And guess what? It's not even new anymore - it came out in 2015! That's all the more reason that it's time for us to understand
what it brings. Because if you eventually want to use a frontend framework like ReactJS, you need to know this stuff!

Project Setup
Before we dive in and meet Godzilla, you should totally code along with me. To do that, use the download button on this page
to get the course code. When you unzip it, there will be a start/ directory which will have the same code that I have here. Check
out the README.md file for witty banter and setup instructions.

The last step will be to open a terminal, go into the directory and run:

$ php bin/console server:run

to start the built-in PHP web server. Find your browser and open http://localhost:8000 . You can login with ron_furgandy ,
password pumpup . Welcome back to LiftStuff . Our app for keeping track of everything we lift during the day to stay in top
shape. This is a Symfony application, but that's not really important: almost everything we've been doing lives inside a single
JavaScript file called RepLogApp.js .

New Shiny JavaScript Version?


Now, about this new JavaScript version. From time to time, every language comes out with new versions. In our world, the PHP
core team dreams up some new ideas, writes a bunch of code, and then releases it. We all happily install the new version on
our servers and use the cool new stuff!

JavaScript is no different. Wait, that's not right! JavaScript is totally different. When a new version of JavaScript comes out, well,
what does that even mean? Because there isn't just one JavaScript, there are many: Chrome has a JavaScript engine, Internet
Explorer maintains its own... crappy one, and of course, Node.js is another JavaScript engine. So when the JavaScript
community decides it wants to add a new function... well, it can't! All it can really do is recommend that the new function be
added... and then wait for all the browsers to add it!

There is only ECMAScript


Ok, the situation isn't that bad. But, JavaScript is not a language like PHP that has one core code. In reality, JavaScript is
nothing more than a standard. When a new version of JavaScript is released, it simply means that the core group has said:

Here are some functions and language changes that we think would make JavaScript more hipster. Now, quick,
everyone go and implement these!

And guess what? The language isn't even called JavaScript! It's called ECMAScript. And there is a group of smart people that
work on new versions of ECMAScript. But unlike PHP, that doesn't mean they're writing code: they're simply deciding what
should be included in the next version. Then, it's up to each browser and JavaScript engine to implement that. But as we will
learn later... some smart people in the JS world have found a way around needing to wait for browser support...

ECMAScript 2015?
Back to the story: in 2015 - after over 5 years of work - ECMAScript released a new version. What's it called? Um, yea, it has a
bunch of names: ES6, ECMAScript 6, ECMAScript 2015, Harmony, or, to its closest friends, Larry. Ok, maybe nobody calls it
Larry.
The official name is ECMAScript 2015, though you'll even hear me call it ES6 because it's the sixth version of ECMAScript.

As we'll learn, ES2015 comes with a lot of new functions and tools. But, more importantly, it comes with new language
constructs - new syntaxes that weren't allowed before. In fact, it comes with so many new syntaxes, that if you look at a
JavaScript file that uses everything, you might not even recognize it as JavaScript.

And that's not okay: because you and I, we need to be able to understand and write modern JavaScript.

 Tip

There is already another new version of ECMAScript: ECMAScript 2016. But it only contains a few, minor features.

So here's our mission: jump into the important stuff of ES2015 that will let us understand, and write truly, modern JavaScript.
Let's start with my absolute favorite, game-changing feature of ES2015: arrow functions.
Chapter 2: Arrow Functions

You will see the first big feature or ES2015 used everywhere... and at first, it looks weird. Very simply, there is a new, shorter
syntax for creating anonymous functions.

For example, in our .then() , we have an anonymous function:

 193 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs: function() {
33 var self = this;
34 $.ajax({
35 url: Routing.generate('rep_log_list'),
36 }).then(function(data) {
37 $.each(data.items, function(key, repLog) {
38 self._addRow(repLog);
39 });
40 })
41 },
 ... lines 42 - 173
174 });
 ... lines 175 - 191
192 })(window, jQuery, Routing, swal);

In ES2015, we can remove the word function , and add an "equal arrow" ( => ) after the arguments:

 193 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs: function() {
33 var self = this;
34 $.ajax({
35 url: Routing.generate('rep_log_list'),
36 }).then((data) => {
37 $.each(data.items, function(key, repLog) {
38 self._addRow(repLog);
39 });
40 })
41 },
 ... lines 42 - 173
174 });
 ... lines 175 - 191
192 })(window, jQuery, Routing, swal);

That's it! That will do the exact same thing as before. Well, PhpStorm is really angry about this, but ignore it for a second. Let's
try it! This loadRepLogs() function is called on page-load to populate the table. Refresh!

It works: no errors.
Make PhpStorm Less Angry
But, apparently PhpStorm hates the arrow function! That's because it's setup to only recognize old, ES5 JavaScript.

Go into your settings and search for ES6. Under "Languages & Frameworks", "JavaScript", you can choose what version it
should use. Let's go with "ECMAScript 6". Hit ok... and once it's done indexing... ding! It's happy! And I'm happy too!

If you see a bubble about a "File Watcher to transpile using Babel", ignore that! But, we will talk about that "Babel" thing later,
it's more than just a cool-sounding word. Babel.

Different Arrow Syntaxes


So the arrow syntax is nothing Earth-shattering. But it's used a lot, so you need to train your eyes to recognize that it's just an
anonymous function.

And sometimes, it can look a bit different. For example, the parentheses around the arguments? Totally optional! Without them,
everything still works:

 193 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs: function() {
33 var self = this;
34 $.ajax({
35 url: Routing.generate('rep_log_list'),
36 }).then(data => {
37 $.each(data.items, function(key, repLog) {
38 self._addRow(repLog);
39 });
40 })
41 },
 ... lines 42 - 173
174 });
 ... lines 175 - 191
192 })(window, jQuery, Routing, swal);

I like the parentheses: I feel like it gives my arrow functions a bit more structure. But other code might not have them.

The Arrow Function's (Secret) Superpower (this)


Now if this were all the arrow function did, I would be pretty disappointed. After all, did we really need a new syntax, just to save
us from typing the word function? Well don't worry, because the arrow function has one, very amazing super power.

To show it off, inside of the anonymous function, console.log(this, self) :


 194 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs: function() {
33 var self = this;
34 $.ajax({
35 url: Routing.generate('rep_log_list'),
36 }).then(data => {
37 console.log(this, self);
 ... lines 38 - 40
41 })
42 },
 ... lines 43 - 174
175 });
 ... lines 176 - 192
193 })(window, jQuery, Routing, swal);

We know that inside of an anonymous function, this always changes to be something different. And that's why we added the
self variable: it allows us to refer to our RepLogApp object from inside the callback.

Ok, find your browser and refresh! Woh, check this out: this appears to be our RepLogApp object! Yea, this and self are the
same thing! What!?

It turns out, a classic anonymous function and the new arrow function do have one difference: when you use an arrow function,
the this variable is preserved. That's awesome news, and it's why I now use the arrow function everywhere in my code.

We can finally remove this silly var = self thing. And instead, below, use this . But because we're inside of another anonymous
function, replace it with the new arrow syntax to get things work:

 192 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs: function() {
33 $.ajax({
34 url: Routing.generate('rep_log_list'),
35 }).then(data => {
36 $.each(data.items, (key, repLog) => {
37 this._addRow(repLog);
38 });
39 })
40 },
 ... lines 41 - 172
173 });
 ... lines 174 - 190
191 })(window, jQuery, Routing, swal);

Try that out! It still works!

Arrow Functions Everywhere!


Let's use the arrow syntax everywhere! Below, when we use SweetAlert, remove the self variable and - in preConfirm - use ()
for empty arguments, then add the arrow. Inside, we can use this ! Use the arrow function again below:
 190 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 47
48 handleRepLogDelete: function (e) {
 ... lines 49 - 52
53 swal({
 ... lines 54 - 57
58 preConfirm: () => {
59 return this._deleteRepLog($link);
60 }
61 }).catch((arg) => {
62 // canceling is cool!
63 });
64 },
 ... lines 65 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Here, we're not using this , but I like to stay consistent and use the arrow function everywhere.

Keep going! Inside the next method, remove self , and add our arrow function:

 190 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 65
66 _deleteRepLog: function($link) {
 ... lines 67 - 78
79 }).then(() => {
 ... lines 80 - 83
84 })
85 },
 ... lines 86 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Do the same for fadeOut() . But here, we were using this , which previously pointed to the DOM Element object that was fading
out. We can't use this anymore, but that's fine! Replace it with $row.remove() and then this.updateTotalWeight() :
 190 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 65
66 _deleteRepLog: function($link) {
 ... lines 67 - 78
79 }).then(() => {
80 $row.fadeOut('normal', () => {
81 $row.remove();
82 this.updateTotalWeightLifted();
83 });
84 })
85 },
 ... lines 86 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Double-check that things work. Refresh! Delete one of the items and... perfect!

Since we're going to use arrow functions for all anonymous functions, search for function() . Yep, we're going to replace
everything, except for the methods in our objects. Remove function() , and add the arrow:

 190 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 90
91 handleNewFormSubmit: function(e) {
 ... lines 92 - 95
96 $.each($form.serializeArray(), (key, fieldData) => {
 ... line 97
98 });
 ... lines 99 - 106
107 },
 ... lines 108 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Repeat it again, and remove another self variable: just use this :
 190 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 90
91 handleNewFormSubmit: function(e) {
 ... lines 92 - 100
101 .then((data) => {
102 this._clearForm();
103 this._addRow(data);
104 }).catch((errorData) => {
105 this._mapErrorsToForm(errorData.errors);
106 });
107 },
 ... lines 108 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

I'll fast-forward through the rest of the changes.

Looping without Using this


If you were watching really closely, you may have noticed a problem. Before, inside the $.each() callback, this was the
element that we were iterating over at that exact time:

 190 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 178
179 $.extend(Helper.prototype, {
180 calculateTotalWeight: function() {
 ... line 181
182 this.$wrapper.find('tbody tr').each(function () {
183 totalWeight += $(this).data('weight');
184 });
 ... lines 185 - 186
187 }
188 });
189 })(window, jQuery, Routing, swal);

But now that we're using the arrow function, obviously, that won't work. No worries! Just give your arrow function two
arguments: index and element . Use element instead of this :

 190 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 178
179 $.extend(Helper.prototype, {
180 calculateTotalWeight: function() {
 ... line 181
182 this.$wrapper.find('tbody tr').each((index, element) => {
183 totalWeight += $(element).data('weight');
184 });
 ... lines 185 - 186
187 }
188 });
189 })(window, jQuery, Routing, swal);

If we search for .each() , there is one other spot with the same problem. Same solution: add index, element and use element
inside:

 190 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 129
130 _mapErrorsToForm: function(errorData) {
 ... lines 131 - 133
134 $form.find(':input').each((index, element) => {
135 var fieldName = $(element).attr('name');
136 var $wrapper = $(element).closest('.form-group');
 ... lines 137 - 145
146 });
147 },
 ... lines 148 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Arrow Function without a Body


Now that we're using the arrow function everywhere, there's one more variation that you'll see. Scroll back up and find the
preConfirm option on SweetAlert:

 190 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 47
48 handleRepLogDelete: function (e) {
 ... lines 49 - 52
53 swal({
 ... lines 54 - 57
58 preConfirm: () => {
59 return this._deleteRepLog($link);
60 }
 ... lines 61 - 62
63 });
64 },
 ... lines 65 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

In this case, the arrow function is nothing more than a single return statement. In this situation, to be extra fancy, you can
remove the function body and return statement entirely:
 188 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 47
48 handleRepLogDelete: function (e) {
 ... lines 49 - 52
53 swal({
 ... lines 54 - 57
58 preConfirm: () => this._deleteRepLog($link)
 ... lines 59 - 60
61 });
62 },
 ... lines 63 - 168
169 });
 ... lines 170 - 186
187 })(window, jQuery, Routing, swal);

When you don't have the curly braces, it means that this value will be returned. It looks weird at first, but it means the same
thing that we had before. You will see this kind of stuff in code examples.

Phew! After making all these changes, let's refresh and try them. The list loads, we can delete, and the form still validates.
Bananas!

Next, I think we should write some Node.js!


Chapter 3: Rocking Some NodeJS

I know this course is aimed at using JavaScript in a browser. Even still... we need to talk about Node.js! If you haven't used it
before, Node.js is basically JavaScript... that you execute on your server! In the same way that we can write a PHP file and run
it... we can do the same thing with Node.js: write a JavaScript file, execute it from the command line, and see the output!

So why are we talking about it? Well, it's going to give us a really easy way to test out some of these new ES2015 features. And
in the next tutorial on Webpack, we'll be working with it even more.

Creating a Simple Node.js Script


Step one for you is to install Node.js. Because I'm on a Mac, I've already installed it using Brew. If you don't have it installed,
just check out their docs - it's a bit different on every system.

Once you're ready, you should be able to execute node -v from the command line. Ok, let's experiment! At the root of your
project, create a new file called play.js ... because of course, Node.js is JavaScript!

Inside, use the familiar console.log('OMG! Node is JS on the server!') :

2 lines play.js 
1 console.log('OMG! Node is JS on the server!');

Now, how do we run that? Simple:

$ node play.js

Boom! And now we can start experimenting with new ES2015 features... without needing to constantly refresh the browser.
Let's play a bit more with our arrow functions. Create a new variable called aGreatNumber set to 10:

 8 lines play.js 
1 var aGreatNumber = 10;
 ... lines 2 - 8

Then, call setTimeout() and pass it an arrow function. Inside, console.log(aGreatNumber) :

 8 lines play.js 
1 var aGreatNumber = 10;
2
3 setTimeout(() => {
4 console.log(aGreatNumber);
5 }, 1000);
 ... lines 6 - 8

Delay that call for 1 second, and, at the bottom, just log waiting :

8 lines play.js 
1 var aGreatNumber = 10;
2
3 setTimeout(() => {
4 console.log(aGreatNumber);
5 }, 1000);
6
7 console.log('waiting...');
Head back to the terminal and run that! It prints, waits and prints! Sweet! Now let's go learn about let and const !
Chapter 4: var Versus let: Scope!

When you look at ES2015 code, one thing tends to jump out immediately: suddenly instead of seeing var everywhere... you
see something called let ! In our JS file, if you scroll down to the bottom, PhpStorm has highlighted my var with a warning:

var used instead of let or const

What's going on?

To find out, change var to let and then console.log(totalWeight) :

 189 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 176
177 $.extend(Helper.prototype, {
178 calculateTotalWeight: function() {
179 let totalWeight = 0;
180 this.$wrapper.find('tbody tr').each((index, element) => {
 ... line 181
182 });
183 console.log(totalWeight);
 ... lines 184 - 185
186 }
187 });
188 })(window, jQuery, Routing, swal);

Now, go refresh. This function is called a bunch of times as the table is loading. And... it looks like everything works fine. It
looks like var and let are equivalent?

In most cases, that's true! let is a new way to initialize a variable that's almost the same as var . 99% of the time, you can use
either one, and it won't make a difference.

Understanding the Scope of var


So... what about that last 1%? Well, it has to do with variable scope. To understand, go back to play.js . At the top, add
something silly: if (true) then aGreatNumber = 42 , which of course, is a great number:

 12 lines play.js 
1 var aGreatNumber = 10;
2
3 if (true) {
4 aGreatNumber = 42;
5 }
 ... lines 6 - 12

When we run it, it re-assigns the variable to 42. No surprises. But what if we added var aGreatNumber = 42 ?

 12 lines play.js 
1 var aGreatNumber = 10;
2
3 if (true) {
4 var aGreatNumber = 42;
5 }
 ... lines 6 - 12
I shouldn't need to say var again: the variable has already been initialized. But, will this give us an error? Or change anything?

Let's find out! No! We still see 42. When we use the second var , it re-declares a new variable called aGreatNumber . But that
doesn't make any real difference: down below, it prints the new variable's value: 42.

But now, wrap this same code in a self-executing function. Use the new arrow syntax to be trendy, then execute it immediately:

 14 lines play.js 
1 var aGreatNumber = 10;
2
3 if (true) {
4 (() => {
5 var aGreatNumber = 42;
6 })();
7 }
 ... lines 8 - 14

Will this change anything? Try it!

Woh! It prints as 10! Why!?

Remember, the scope of a variable created with var is whatever function it is inside of. Let's follow the code. First, we create
the variable and set it to 10. Then we create a new variable set to 42. But since this is inside of a function, its scope is only this
function. In other words, inside of the self-executing block, aGreatNumber is 42. But outside, the original variable still exists, and
it's still set to 10. Since we're printing it from outside the function, we see 10.

Okay okay, I know, this can be confusing. And most of the time... this subtle scope stuff doesn't make any difference. But, this is
exactly where var and let different.

The "Block" Scope of let


Let me show you. Remove the self-executing function and change each var to let :

 12 lines play.js 
1 let aGreatNumber = 10;
2
3 if (true) {
4 let aGreatNumber = 42;
5 }
 ... lines 6 - 12

If let and var behaved exactly the same, we would expect this - just like before - to print 42. Try it.

But no! It prints 10! And this is the difference between var and let . With var - just like with any variable in PHP - a variable's
scope is the function it's inside of, plus any embedded functions. But let is different: it's said to be "block-scoped". That means
that anytime you have a new open curly brace ( { ) - like an if statement or for loop - you've entered a new scope for let . In this
case, let is equal to 42, only inside of the if statement. Outside, it's a completely different variable, which is set to 10.

Of course, if we remove the extra let statement and try it, now we get 42:

 12 lines play.js 
1 let aGreatNumber = 10;
2
3 if (true) {
4 aGreatNumber = 42;
5 }
 ... lines 6 - 12

This is because without the let , we're no longer creating a new variable: we're simply changing the existing variable to 42.

If this makes your head spin, me too! In practice, there are very few situations where var and let behave different. So, use your
favorite. But there is one other tiny thing that makes me like let , and it deals with variable hoisting.
Chapter 5: var Versus let: Hoisting!

There's one other reason to use let instead of var . To understand it, we need to get really nerdy and talk about something with
a cool name: variable ahoy-sting. I mean, variable hoisting.

At the top of the play file, do something terrible: console.log(bar) :

 13 lines play.js 
1 console.log(bar);
2 let aGreatNumber = 10;
 ... lines 3 - 13

I know! This doesn't even make sense - there is no variable called bar ! When we try it, we get:

ReferenceError: bar is not defined

No surprise! If we try to log aGreatNumber , the same thing happens!

 13 lines play.js 
1 console.log(aGreatNumber);
2 let aGreatNumber = 10;
 ... lines 3 - 13

The variable has not been initialized yet.

Ready for things to get weird? Change the let to var :

 13 lines play.js 
1 console.log(aGreatNumber);
2 var aGreatNumber = 10;
 ... lines 3 - 13

And all of a sudden, it does not break. It simply says that that value is undefined .

Hello Mr Variable Hoisting


The reason for this is something called variable hoisting, a term you'll see a lot around JavaScript... I think mostly because it
has a cool name. It's actually not that important, but I want to tell you a little bit about it so you don't have to worry about it ever
again.

In PHP, we never need to initialize a variable with a special keyword. We don't say var $aGreatNumber = 10 , we just say
$aGreatNumber = 10 and we're good to go. But in many other languages, including JavaScript, you must initialize a variable first
with a keyword.

When you use var to initialize a variable, when JavaScript executes, it basically finds all of your var variables, goes to the top
of that variable's scope - usually the top of whatever function it's inside of, but in this case, it's the top of the file - and effectively
does this: var aGreatNumber . That initializes the variable, but doesn't set it to any value. This is called variable hoisting: and it's
the reason that we get undefined instead of an error when we try to use a variable that's declared with var ... before it's
declared.

But when we change this to let , we already saw that this does throw a ReferenceError . And that's kinda great! I mean, isn't that
what we would expect to happen when we reference a variable that hasn't been created yet!

So with var , variables are hoisted to the top. But with let , that doesn't happen, and that's kinda cool. Well, technically, let also
does variable hoisting, but thanks to something called the "temporal dead zone" - also an awesome name - let acts normal: as
if its variables were not hoisted.
Since let seems to behave more predictably, let's go into RepLogApp and change all of these "vars" to let . Find all "var space"
and replace with "let space":

 188 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 47
48 handleRepLogDelete: function (e) {
 ... lines 49 - 50
51 let $link = $(e.currentTarget);
 ... lines 52 - 61
62 },
63
64 _deleteRepLog: function($link) {
 ... lines 65 - 70
71 let deleteUrl = $link.data('url');
72 let $row = $link.closest('tr');
 ... lines 73 - 82
83 },
 ... lines 84 - 88
89 handleNewFormSubmit: function(e) {
 ... lines 90 - 91
92 let $form = $(e.currentTarget);
93 let formData = {};
 ... lines 94 - 104
105 },
106
107 _saveRepLog: function(data) {
108 return new Promise((resolve, reject) => {
109 $.ajax({
 ... lines 110 - 112
113 }).then((data, textStatus, jqXHR) => {
 ... lines 114 - 119
120 }).catch((jqXHR) => {
121 let errorData = JSON.parse(jqXHR.responseText);
 ... lines 122 - 123
124 });
125 });
126 },
127
128 _mapErrorsToForm: function(errorData) {
 ... line 129
130 let $form = this.$wrapper.find(this._selectors.newRepForm);
131
132 $form.find(':input').each((index, element) => {
133 let fieldName = $(element).attr('name');
134 let $wrapper = $(element).closest('.form-group');
 ... lines 135 - 139
140 let $error = $('<span class="js-field-error help-block"></span>');
 ... lines 141 - 143
144 });
145 },
146
147 _removeFormErrors: function() {
148 let $form = this.$wrapper.find(this._selectors.newRepForm);
 ... lines 149 - 150
151 },
152
153 _clearForm: function() {
153 _clearForm: function() {
 ... lines 154 - 155
156 let $form = this.$wrapper.find(this._selectors.newRepForm);
 ... line 157
158 },
159
160 _addRow: function(repLog) {
161 let tplText = $('#js-rep-log-row-template').html();
162 let tpl = _.template(tplText);
163
164 let html = tpl(repLog);
 ... lines 165 - 167
168 }
169 });
170
171 /**
172 * A "private" object
173 */
174 let Helper = function ($wrapper) {
175 this.$wrapper = $wrapper;
176 };
177 $.extend(Helper.prototype, {
178 calculateTotalWeight: function() {
179 let totalWeight = 0;
 ... lines 180 - 184
185 }
186 });
187 })(window, jQuery, Routing, swal);

And just to make sure that our code doesn't have any edge cases where var and let behave differently, try out the page! Yay!
Everything looks like it's still working great.

Now, what about the new const keyword?


Chapter 6: const Versus let

The ECMAScript gods didn't stop with let - they added a third way to declare a new variable: var , let and cat . Wait, that's not
right - var , let and const .

Back in our play file, remove the log and initialize the variable with const :

 12 lines play.js 
1 const aGreatNumber = 10;
2
3 if (true) {
4 aGreatNumber = 42;
5 }
 ... lines 6 - 12

As you're probably already guessing - and as you can see from PhpStorm being very angry - when you initialize a variable with
const , it can't be reassigned.

When we try this now, we get a huge error!

TypeError: Assignment to constant variable.

But if we comment-out the line where we change the variable, it works just like we expect:

 12 lines play.js 
1 const aGreatNumber = 10;
2
3 if (true) {
4 //aGreatNumber = 42;
5 }
 ... lines 6 - 12

As far as scope goes, const and let work the same. So really, const and let are identical... except that you can't modify a
const variable.

You can Change a const?


Well actually, that's not completely accurate. Create another const variable called aGreatObject . Inside it, set withGreatKeys to
true . Below, change that object: aGreatObject.withGreatKeys = false :

 16 lines play.js 
1 const aGreatNumber = 10;
2 const aGreatObject = { withGreatKeys: true };
3
4 aGreatObject.withGreatKeys = false;
 ... lines 5 - 16

Log that object at the bottom:


 16 lines play.js 
1 const aGreatNumber = 10;
2 const aGreatObject = { withGreatKeys: true };
3
4 aGreatObject.withGreatKeys = false;
 ... lines 5 - 9
10 setTimeout(() => {
11 console.log(aGreatNumber);
12 console.log(aGreatObject)
13 }, 1000);
 ... lines 14 - 16

Will this work?

Try it! It does work! The withGreatKeys property did change! Here's the truth: when you use const , it's not that the value of that
variable can't change. The object can change. Instead, using const means that you cannot reassign the aGreatObject variable
to something else in memory. It must be assigned only once, to this object. But after that, the object is free to change.

var, let and const


Phew! Okay, in ES2015, var , let , and const do have some differences. But a lot of the time, you will see people use one or the
other purely based on personal preference, or whatever is trendy that week. The differences are minor.

In our case, because you know I love to write hipster code, let's change each let to const . Start at the top: const $link makes
sense. We don't need to reassign that. The same is true for deleteUrl , $row , $form , and formData :

 188 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 47
48 handleRepLogDelete: function (e) {
 ... lines 49 - 50
51 const $link = $(e.currentTarget);
 ... lines 52 - 61
62 },
63
64 _deleteRepLog: function($link) {
 ... lines 65 - 70
71 const deleteUrl = $link.data('url');
72 const $row = $link.closest('tr');
 ... lines 73 - 82
83 },
 ... lines 84 - 88
89 handleNewFormSubmit: function(e) {
 ... lines 90 - 91
92 const $form = $(e.currentTarget);
93 const formData = {};
 ... lines 94 - 104
105 },
106
107 _saveRepLog: function(data) {
108 return new Promise((resolve, reject) => {
109 $.ajax({
 ... lines 110 - 112
113 }).then((data, textStatus, jqXHR) => {
 ... lines 114 - 119
120 }).catch((jqXHR) => {
121 const errorData = JSON.parse(jqXHR.responseText);
 ... lines 122 - 123
124 });
125 });
126 },
127
128 _mapErrorsToForm: function(errorData) {
 ... line 129
130 const $form = this.$wrapper.find(this._selectors.newRepForm);
131
132 $form.find(':input').each((index, element) => {
133 const fieldName = $(element).attr('name');
134 const $wrapper = $(element).closest('.form-group');
 ... lines 135 - 139
140 const $error = $('<span class="js-field-error help-block"></span>');
 ... lines 141 - 143
144 });
145 },
146
147 _removeFormErrors: function() {
148 const $form = this.$wrapper.find(this._selectors.newRepForm);
 ... lines 149 - 150
151 },
152
153 _clearForm: function() {
 ... lines 154 - 155
156 const $form = this.$wrapper.find(this._selectors.newRepForm);
 ... line 157
158 },
159
160 _addRow: function(repLog) {
161 const tplText = $('#js-rep-log-row-template').html();
162 const tpl = _.template(tplText);
163
164 const html = tpl(repLog);
 ... lines 165 - 167
168 }
169 });
170
171 /**
172 * A "private" object
173 */
174 const Helper = function ($wrapper) {
175 this.$wrapper = $wrapper;
176 };
 ... lines 177 - 186
187 })(window, jQuery, Routing, swal);

Sure, we modify formData , but we know that's ok with const !

Keep going: $form , fieldName , $wrapper , $error ... and eventually we get to the last one: totalWeight . But this variable can't be
set to a const : we set it to 0, but then reassign it in each loop. This is a perfect case for let :
 188 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 176
177 $.extend(Helper.prototype, {
178 calculateTotalWeight: function() {
179 let totalWeight = 0;
 ... lines 180 - 184
185 }
186 });
187 })(window, jQuery, Routing, swal);

Let's take our app for a spin! Refresh! And try deleting something. Woohoo! Yep, you can pretty much choose between var , let
and cat , I mean, const .
Chapter 7: Object Literals & Optional Args

When it comes to functions and arrays, ES2015 has a couple of things you are going to love! Well, some of this stuff might look
weird at first... but then you will love them!

Object Keys... without the Key


Start in _saveRepLog . Right now, we're passing $.ajax() some options: url , method and data :

 188 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 106
107 _saveRepLog: function(data) {
108 return new Promise((resolve, reject) => {
109 $.ajax({
110 url: Routing.generate('rep_log_new'),
111 method: 'POST',
112 data: JSON.stringify(data)
113 }).then((data, textStatus, jqXHR) => {
 ... lines 114 - 123
124 });
125 });
126 },
 ... lines 127 - 168
169 });
 ... lines 170 - 186
187 })(window, jQuery, Routing, swal);

Above that line, create a new url variable set to the URL:

 190 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 106
107 _saveRepLog: function(data) {
108 return new Promise((resolve, reject) => {
109 const url = Routing.generate('rep_log_new');
110
111 $.ajax({
 ... lines 112 - 125
126 });
127 });
128 },
 ... lines 129 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Then, use that below:


 190 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 106
107 _saveRepLog: function(data) {
108 return new Promise((resolve, reject) => {
109 const url = Routing.generate('rep_log_new');
110
111 $.ajax({
112 url: url,
113 method: 'POST',
114 data: JSON.stringify(data)
115 }).then((data, textStatus, jqXHR) => {
 ... lines 116 - 125
126 });
127 });
128 },
 ... lines 129 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Obviously, this will work exactly like before: nothing interesting yet. Well, in ES2015, if your key and your value are the same,
you can just leave off the key:

 190 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 106
107 _saveRepLog: function(data) {
108 return new Promise((resolve, reject) => {
109 const url = Routing.generate('rep_log_new');
110
111 $.ajax({
112 url,
113 method: 'POST',
114 data: JSON.stringify(data)
115 }).then((data, textStatus, jqXHR) => {
 ... lines 116 - 125
126 });
127 });
128 },
 ... lines 129 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Yep, this means the same thing as before. So if you suddenly see an associative array or object where one of its keys is
missing... well, it is the key... and the value.

Short Method Syntax


Next, you can do something similar with methods inside an object. The loadRepLogs() method is just a loadRepLogs key
assigned to a function:
 190 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs: function() {
 ... lines 33 - 39
40 },
 ... lines 41 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

Simple, but too much work, maybe? In ES2015, we can shorten this to loadRepLogs() :

 190 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs() {
 ... lines 33 - 39
40 },
 ... lines 41 - 170
171 });
 ... lines 172 - 188
189 })(window, jQuery, Routing, swal);

So much cooler! Let's change it everywhere! Search for the word function , because almost everything is about to change:
 194 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 31
32 loadRepLogs() {
 ... lines 33 - 39
40 },
41
42 updateTotalWeightLifted() {
 ... lines 43 - 45
46 },
47
48 handleRepLogDelete(e) {
 ... lines 49 - 61
62 },
63
64 _deleteRepLog($link) {
 ... lines 65 - 82
83 },
84
85 handleRowClick() {
 ... line 86
87 },
88
89 handleNewFormSubmit(e) {
 ... lines 90 - 104
105 },
106
107 _saveRepLog(data) {
 ... lines 108 - 127
128 },
129
130 _mapErrorsToForm(errorData) {
 ... lines 131 - 146
147 },
148
149 _removeFormErrors() {
 ... lines 150 - 152
153 },
154
155 _clearForm() {
 ... lines 156 - 159
160 },
161
162 _addRow(repLog) {
 ... lines 163 - 169
170 }
171 });
 ... lines 172 - 178
179 $.extend(Helper.prototype, {
180 calculateTotalWeight() {
 ... lines 181 - 186
187 },
 ... lines 188 - 191
192 });
193 })(window, jQuery, Routing, swal);

Ultimately, the only function keywords that will be left are for the self-executing function - which could be an arrow function - and
the two constructors:
 194 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 window.RepLogApp = function ($wrapper) {
 ... lines 5 - 24
25 };
 ... lines 26 - 175
176 const Helper = function ($wrapper) {
177 this.$wrapper = $wrapper;
178 };
 ... lines 179 - 192
193 })(window, jQuery, Routing, swal);

Nice!

Optional Args
Ready for one more cool thing? This one is easy. Suppose we have a new method, not calculateTotalWeight() , but
getTotalWeightString() . Use the new shorthand syntax and return this.calculateTotalWeight() and append "pounds" to it:

 194 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 178
179 $.extend(Helper.prototype, {
 ... lines 180 - 188
189 getTotalWeightString() {
190 return this.calculateTotalWeight() + ' lbs';
191 }
192 });
193 })(window, jQuery, Routing, swal);

Perfect! Then above, in updateTotalWeightLifted() , instead of calling calculateTotalWeight() and passing that to .html() , pass
getTotalWeightString() :

 194 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 41
42 updateTotalWeightLifted() {
43 this.$wrapper.find('.js-total-weight').html(
44 this.helper.getTotalWeightString()
45 );
46 },
 ... lines 47 - 170
171 });
 ... lines 172 - 192
193 })(window, jQuery, Routing, swal);

Ok, nothing too crazy so far: when we refresh, at the bottom, yep, "pounds".

But now suppose that we want to set a max weight on that. What I mean is, if we are over a certain weight - maybe 500 -
instead of printing the actual total, we want to print "500+"

Start by adding a new argument called maxWeight . Then say let weight = this.calculateTotalWeight() . And if weight > maxWeight ,
add weight = maxWeight + '+' . At the bottom, return weight and "pounds":
 200 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 178
179 $.extend(Helper.prototype, {
 ... lines 180 - 188
189 getTotalWeightString(maxWeight) {
190 let weight = this.calculateTotalWeight();
191
192 if (weight > maxWeight) {
193 weight = maxWeight + '+';
194 }
195
196 return weight + ' lbs';
197 }
198 });
199 })(window, jQuery, Routing, swal);

Head up top to try this: when we call getTotalWeightString() , pass 500:

 200 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 41
42 updateTotalWeightLifted() {
43 this.$wrapper.find('.js-total-weight').html(
44 this.helper.getTotalWeightString(500)
45 );
46 },
 ... lines 47 - 170
171 });
 ... lines 172 - 198
199 })(window, jQuery, Routing, swal);

Refresh! It works! We see the 500+ at the bottom.

But what if I wanted to make this argument optional with a default value of 500? You could do this before in JavaScript, but it
was ugly. Now, thanks to our new best friend ES2015, we can say maxWeight = 500 - the same way we do in PHP:

 200 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 178
179 $.extend(Helper.prototype, {
 ... lines 180 - 188
189 getTotalWeightString(maxWeight = 500) {
 ... lines 190 - 196
197 }
198 });
199 })(window, jQuery, Routing, swal);

Now, we can remove the argument and everything is still happy!


 200 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 26
27 $.extend(window.RepLogApp.prototype, {
 ... lines 28 - 41
42 updateTotalWeightLifted() {
43 this.$wrapper.find('.js-total-weight').html(
44 this.helper.getTotalWeightString()
45 );
46 },
 ... lines 47 - 170
171 });
 ... lines 172 - 198
199 })(window, jQuery, Routing, swal);

So, yay! Finally, JavaScript has optional function arguments! And a second yay, because we are ready to learn perhaps the
biggest change in ES2015: JavaScript classes.
Chapter 8: Legit JavaScript Classes

In the first JavaScript tutorial, we learned about objects. I mean, real objects: the kind you can instantiate by creating a
constructor function, and then adding all the methods via the prototype. Objects look a lot different in PHP than in in JavaScript,
in large part because PHP has classes and JavaScript doesn't. Well... that's a big fat lie! ES2015 introduces classes: true
classes.

Creating a new class


As a PHP developer, you're going to love this... because the class structure looks nearly identical to PHP! If you want to create
a Helper class... just say, class Helper {} :

 203 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 172
173 /**
174 * A "private" object
175 */
176 class Helper {
177
178 }
 ... lines 179 - 201
202 })(window, jQuery, Routing, swal);

That's it! With this syntax, the constructor is called, just, constructor . Move the old constructor function into the class and rename
it: constructor . You can also remove the semicolon after the method, just like in PHP:

 201 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 172
173 /**
174 * A "private" object
175 */
176 class Helper {
177 constructor($wrapper) {
178 this.$wrapper = $wrapper;
179 }
 ... lines 180 - 198
199 }
200 })(window, jQuery, Routing, swal);

Moving everything else into the new class syntax is easy: remove $.extend(helper.prototype) and move all of the methods inside
of the class:
 201 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 172
173 /**
174 * A "private" object
175 */
176 class Helper {
177 constructor($wrapper) {
178 this.$wrapper = $wrapper;
179 }
180
181 calculateTotalWeight() {
182 let totalWeight = 0;
183 this.$wrapper.find('tbody tr').each((index, element) => {
184 totalWeight += $(element).data('weight');
185 });
186
187 return totalWeight;
188 }
189
190 getTotalWeightString(maxWeight = 500) {
191 let weight = this.calculateTotalWeight();
192
193 if (weight > maxWeight) {
194 weight = maxWeight + '+';
195 }
196
197 return weight + ' lbs';
198 }
199 }
200 })(window, jQuery, Routing, swal);

And congratulations! We just created a new ES2015 class. Wasn't that nice?

To make things sweeter, it all works just like before: nothing is broken. And that's no accident: behind the scenes, JavaScript
still follows the prototypical object oriented model. This new syntax is just a nice wrapper around it. It's great: we don't need to
worry about the prototype , but ultimately, that is set behind the scenes.

Let's make the same change at the top with RepLogApp : class RepLogApp { and then move the old constructor function inside.
But, make sure to spell that correctly! I'll indent everything and add the closing curly brace:
 203 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
5 constructor($wrapper) {
6 this.$wrapper = $wrapper;
7 this.helper = new Helper(this.$wrapper);
8
9 this.loadRepLogs();
10
11 this.$wrapper.on(
12 'click',
13 '.js-delete-rep-log',
14 this.handleRepLogDelete.bind(this)
15 );
16 this.$wrapper.on(
17 'click',
18 'tbody tr',
19 this.handleRowClick.bind(this)
20 );
21 this.$wrapper.on(
22 'submit',
23 this._selectors.newRepForm,
24 this.handleNewFormSubmit.bind(this)
25 );
26 }
27 }
 ... lines 28 - 201
202 })(window, jQuery, Routing, swal);

Cool! Now we all we need to do is move the methods inside!

Classes do not have Properties


Start by only moving the _selectors property. Paste it inside the class and... woh! PhpStorm is super angry:

Types are not supported by current JavaScript version

Rude! PhpStorm is trying to tell us that properties are not supported inside classes: only methods are allowed. That may seem
weird - but it'll be more clear why in a minute. For now, change this to be a method: _getSelectors() . Add a return statement, and
everything is happy:

 207 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 27
28 _getSelectors() {
29 return {
30 newRepForm: '.js-new-rep-log-form'
31 }
32 }
33 }
 ... lines 34 - 205
206 })(window, jQuery, Routing, swal);

Well, everything except for the couple of places where we reference the _selectors property. Yea, this._selectors , that's not
going to work:
 207 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
5 constructor($wrapper) {
 ... lines 6 - 20
21 this.$wrapper.on(
 ... line 22
23 this._selectors.newRepForm,
 ... line 24
25 );
26 }
 ... lines 27 - 135
136 _mapErrorsToForm(errorData) {
 ... line 137
138 const $form = this.$wrapper.find(this._selectors.newRepForm);
 ... lines 139 - 152
153 },
154
155 _removeFormErrors() {
156 const $form = this.$wrapper.find(this._selectors.newRepForm);
 ... lines 157 - 158
159 },
160
161 _clearForm() {
 ... lines 162 - 163
164 const $form = this.$wrapper.find(this._selectors.newRepForm);
 ... line 165
166 },
 ... lines 167 - 176
177 });
 ... lines 178 - 205
206 })(window, jQuery, Routing, swal);

But don't fix it! Let's come back in a minute.

Right now, move the rest of the methods inside: just delete the } and the prototype line to do it. We can also remove the
comma after each method:
 203 lines web/assets/js/RepLogApp.js 
1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
5 constructor($wrapper) {
 ... lines 6 - 25
26 }
27
28 _getSelectors() {
29 return {
30 newRepForm: '.js-new-rep-log-form'
31 }
32 }
33
34 loadRepLogs() {
 ... lines 35 - 41
42 }
43
44 updateTotalWeightLifted() {
 ... lines 45 - 47
48 }
49
50 handleRepLogDelete(e) {
 ... lines 51 - 63
64 }
65
66 _deleteRepLog($link) {
 ... lines 67 - 84
85 }
86
87 handleRowClick() {
 ... line 88
89 }
90
91 handleNewFormSubmit(e) {
 ... lines 92 - 106
107 }
108
109 _saveRepLog(data) {
 ... lines 110 - 129
130 }
131
132 _mapErrorsToForm(errorData) {
 ... lines 133 - 148
149 }
150
151 _removeFormErrors() {
 ... lines 152 - 154
155 }
156
157 _clearForm() {
 ... lines 158 - 161
162 }
163
164 _addRow(repLog) {
 ... lines 165 - 171
172 }
173 }
 ... lines 174 - 201
202 })(window, jQuery, Routing, swal);
Other than that, nothing needs to change.

Magic get Methods


Time to go back and fix this _getSelectors() problem. The easiest thing would be to update this._selectors to this._getSelectors() .
But, there's a cooler way.

Rename the method back to _selectors() , and then add a "get space" in front of it:

 206 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 27
28 /**
29 * Call like this.selectors
30 */
31 get _selectors() {
 ... lines 32 - 34
35 }
 ... lines 36 - 175
176 }
 ... lines 177 - 204
205 })(window, jQuery, Routing, swal);

Woh! Instantly, PhpStorm is happy: this is a valid syntax. And when you search for _selectors , PhpStorm is happy about those
calls too!

This is the new "get" syntax: a special new feature from ES2015 that allows you to define a method that should be called
whenever someone tries to access a property, like _selectors . There's of course also a "set" version of this, which would be
called when someone tries to set the _selectors property.

So even though classes don't technically support properties, you can effectively create properties by using these get and set
methods.

Oh, and btw, just to be clear: even though you can't define a property on a class, you can still set whatever properties you want
on the object, after it's instantiated:

class CookieJar {
constructor(cookies) {
this.cookies = cookies;
}
}

That hasn't changed.

Ok team! Try out our app! Refresh! It works! Wait, no, an error! Blast! It says:

RepLogApp is not defined

And the error is from our template: app/Resources/views/lift/index.html.twig :


 83 lines app/Resources/views/lift/index.html.twig 
 ... lines 1 - 53
54 {% block javascripts %}
 ... lines 55 - 59
60 <script>
61 $(document).ready(function() {
62 var $wrapper = $('.js-rep-log-table');
63 var repLogApp = new RepLogApp($wrapper);
64 });
65 </script>
 ... lines 66 - 81
82 {% endblock %}

Ah, this code is fine: the problem is that the RepLogApp class only lives within this self executing function:

 206 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 175
176 }
 ... lines 177 - 204
205 })(window, jQuery, Routing, swal);

It's the same problem we had in the first episode with scope.

Solve it in the same way: export the class to the global scope by saying window.RepLogApp = RepLogApp :

 208 lines web/assets/js/RepLogApp.js 


1 'use strict';
2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 175
176 }
 ... lines 177 - 205
206 window.RepLogApp = RepLogApp;
207 })(window, jQuery, Routing, swal);

Try it now! And life is good! So what else can we do with classes? What about static methods?
Chapter 9: Static Class Methods

Go back and look at the new get _selectors() method:

 208 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 30
31 get _selectors() {
32 return {
33 newRepForm: '.js-new-rep-log-form'
34 }
35 }
 ... lines 36 - 175
176 }
 ... lines 177 - 206
207 })(window, jQuery, Routing, swal);

Interesting: PhpStorm is highlighting it like something is wrong! If you hover over it, it says:

Method can be static.

In the first episode, we talked about how when you add your methods to the prototype , it's like creating non-static methods on
PHP classes:

Greeter = function (greeting) {


this.greeting = greeting;
}

Greeter.prototype.sayHi = function () {
console.log(this.greeting);
}

In other words, when you create new instances of your object, each method has access to its own instance properties:

greeter = new Greeter('YO!');


greeter.sayHi(); // YO!

I also said that if you decided not to put a method on the prototype , that is legal, but it effectively becomes static:

Greeter = function (greeting) {


// ...
}

Greeter.sayHi = function () {
console.log('YO!');
}

Greeter.sayHi(); // YO!

If that didn't make a lot of sense then, it's okay. Because with the new class syntax, it's much easier to think about!

PhpStorm is suggesting that this method could be static for one simple reason: the method doesn't use the this variable. That's
the same as in PHP: if a method doesn't use the this variable, it could be made static if we wanted.
It's probably fine either way, but let's make this static! Add static before get _selectors() :

 208 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 30
31 static get _selectors() {
32 return {
33 newRepForm: '.js-new-rep-log-form'
34 }
35 }
 ... lines 36 - 175
176 }
 ... lines 177 - 206
207 })(window, jQuery, Routing, swal);

And as soon as we do that, we can't say this._selectors anymore. Instead, we need to say RepLogApp._selectors :

 208 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 134
135 _mapErrorsToForm(errorData) {
 ... line 136
137 const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
 ... lines 138 - 151
152 }
 ... lines 153 - 175
176 }
 ... lines 177 - 206
207 })(window, jQuery, Routing, swal);

And that makes sense: in PHP, we do the same thing: we use the class name to reference items statically. Let's change that in
a few other places:
 208 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
5 constructor($wrapper) {
 ... lines 6 - 20
21 this.$wrapper.on(
22 'submit',
23 RepLogApp._selectors.newRepForm,
24 this.handleNewFormSubmit.bind(this)
25 );
26 }
 ... lines 27 - 134
135 _mapErrorsToForm(errorData) {
 ... line 136
137 const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
 ... lines 138 - 151
152 }
153
154 _removeFormErrors() {
155 const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
 ... lines 156 - 157
158 }
159
160 _clearForm() {
 ... lines 161 - 162
163 const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
 ... line 164
165 }
 ... lines 166 - 175
176 }
 ... lines 177 - 206
207 })(window, jQuery, Routing, swal);

Perfect!

Time to try things! Refresh! Yes! No errors!

Let's see one more example: scroll all the way down to the Helper class. Create a new method: static _calculateWeight() with an
$elements argument:

 214 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 177
178 /**
179 * A "private" object
180 */
181 class Helper {
 ... lines 182 - 201
202 static _calculateWeights($elements) {
 ... lines 203 - 208
209 }
210 }
 ... lines 211 - 212
213 })(window, jQuery, Routing, swal);

This will be a new static utility method whose job is to loop over whatever elements I pass, look for their weight data attribute,
and then return the total weight:
 214 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 177
178 /**
179 * A "private" object
180 */
181 class Helper {
 ... lines 182 - 201
202 static _calculateWeights($elements) {
203 let totalWeight = 0;
204 $elements.each((index, element) => {
205 totalWeight += $(element).data('weight');
206 });
207
208 return totalWeight;
209 }
210 }
 ... lines 211 - 212
213 })(window, jQuery, Routing, swal);

We don't really need to make this change, but it's valid.

Now, in calculateTotalWeight() , just say: return Helper - because we need to reference the static method by its class name
Helper._calculateTotalWeight() and pass it the elements: this.$wrapper.find('tbody tr') :

 214 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 177
178 /**
179 * A "private" object
180 */
181 class Helper {
 ... lines 182 - 185
186 calculateTotalWeight() {
187 return Helper._calculateWeights(
188 this.$wrapper.find('tbody tr')
189 );
190 }
 ... lines 191 - 209
210 }
 ... lines 211 - 212
213 })(window, jQuery, Routing, swal);

Coolio! Try that out! And we still see the correct total.

And that is 10 times easier to understand as a PHP developer! Sure, JavaScript still has prototypical inheritance behind the
scenes... but most of the time, we won't know or care.
Chapter 10: Class Inheritance and super Calls

In PHP, a class can extend another class. So, can we do that in JavaScript? Totally! And once again, you're going to love it.

To try this out, let's go back to the play.js file. Create a new class: AGreatClass , because, it's going to be a great class. Give it a
constructor with a greatNumber arg and set that on a property:

 16 lines play.js 
1
2 class AGreatClass {
3 constructor(greatNumber) {
4 this.greatNumber = greatNumber;
5 }
 ... lines 6 - 9
10 }
 ... lines 11 - 16

Below, add one new method: returnGreatThings , which it will, because it's going to return our greatNumber !

 16 lines play.js 
1
2 class AGreatClass {
 ... lines 3 - 6
7 returnGreatThings() {
8 return this.greatNumber;
9 }
10 }
 ... lines 11 - 16

Finally, create a new constant, aGreatObject , set to new AGreatClass(42) . Let's console.log(aGreatObject.returnGreatThings()) :

 16 lines play.js 
1
2 class AGreatClass {
 ... lines 3 - 9
10 }
11
12 const aGreatObject = new AGreatClass(42);
13 console.log(
14 aGreatObject.returnGreatThings()
15 );

There's no mystery about what this is going to return. Run the file! Yep, 42!

Extending a Class
Now, let's create another class, class AnotherGreatClass , because we're on a roll. But, I want this to be a sub class of
AGreatClass . How? The same way we do it in PHP: extends AGreatClass :
 20 lines play.js 
1
2 class AGreatClass {
 ... lines 3 - 9
10 }
11
12 class AnotherGreatClass extends AGreatClass{
13
14 }
 ... lines 15 - 20

That is it. We're not overriding anything yet, but this should work. Change the variable to be a new AnotherGreatClass , and then
run the file!

 20 lines play.js 
1
2 class AGreatClass {
 ... lines 3 - 9
10 }
11
12 class AnotherGreatClass extends AGreatClass{
13
14 }
15
16 const aGreatObject = new AnotherGreatClass(42);
 ... lines 17 - 20

Success!

Overriding a Method and Calling super


From here, pretty much all the rules of extending things in PHP work in JavaScript, with just a little bit of a different syntax. For
example, we can override returnGreatThings() . Let's return something else that's great: adventure !

 22 lines play.js 
1
2 class AGreatClass {
 ... lines 3 - 6
7 returnGreatThings() {
8 return this.greatNumber;
9 }
10 }
11
12 class AnotherGreatClass extends AGreatClass{
13 returnGreatThings() {
14 return 'adventure';
15 }
16 }
17
18 const aGreatObject = new AnotherGreatClass(42);
 ... lines 19 - 22

This completely overrides the parent class method.

Okay, but what if we want to call the parent method. In PHP, we would say parent::returnGreatThings() . Well, in JavaScript, the
magic word is super : let greatNumber = super.returnGreatThings() . Let's return an array of great things, like greatNumber and
adventure :
 24 lines play.js 
1
2 class AGreatClass {
 ... lines 3 - 6
7 returnGreatThings() {
8 return this.greatNumber;
9 }
10 }
11
12 class AnotherGreatClass extends AGreatClass{
13 returnGreatThings() {
14 let greatNumber = super.returnGreatThings();
15
16 return [greatNumber, 'adventure'];
17 }
18 }
19
20 const aGreatObject = new AnotherGreatClass(42);
 ... lines 21 - 24

This time, it prints both. Love it!

Overriding the constructor


Can we override the constructor in the same way? Because really, I should be able to pass the greatNumber and the great
thing, adventure , into this class.

Override the constructor and give it just one argument: greatWord . Then, set this.greatWord = greatWord :

 28 lines play.js 
 ... line 1
2 class AGreatClass {
3 constructor(greatNumber) {
4 this.greatNumber = greatNumber;
5 }
 ... lines 6 - 9
10 }
11
12 class AnotherGreatClass extends AGreatClass{
13 constructor(greatWord) {
14 this.greatWord = greatWord;
15 }
 ... lines 16 - 21
22 }
 ... lines 23 - 28

Before we try to print that below, what do you think will happen when we run this? We're setting the greatWord property... but
we're not calling the parent constructor. Actually, try to run it!

Ah, we get an error in the constructor !

ReferenceError: this is not defined

Interesting! Inside construct() , there is no this variable? And PhpStorm is trying to tell us why:

Missed superclass's constructor invocation

Unlike PHP - where calling the parent constructor is usually a good idea, but ultimately optional - in JavaScript, it's required.
You must call the parent's constructor.
So let's give this two arguments: greatNumber and greatWord :

 30 lines play.js 
 ... lines 1 - 11
12 class AnotherGreatClass extends AGreatClass{
13 constructor(greatNumber, greatWord) {
 ... lines 14 - 15
16 this.greatWord = greatWord;
17 }
 ... lines 18 - 23
24 }
 ... lines 25 - 30

To call the parent constructor... it's not what you might think: super.constructor() . You actually treat super like a function:
super(greatNumber) :

 30 lines play.js 
 ... lines 1 - 11
12 class AnotherGreatClass extends AGreatClass{
13 constructor(greatNumber, greatWord) {
14 super(greatNumber);
15
16 this.greatWord = greatWord;
17 }
 ... lines 18 - 23
24 }
 ... lines 25 - 30

Below, let's print out this.greatWord and pass in 42 and adventure :

 30 lines play.js 
 ... lines 1 - 11
12 class AnotherGreatClass extends AGreatClass{
 ... lines 13 - 18
19 returnGreatThings() {
20 let greatNumber = super.returnGreatThings();
21
22 return [greatNumber, this.greatWord];
23 }
24 }
25
26 const aGreatObject = new AnotherGreatClass(42, 'adventure');
 ... lines 27 - 30

Try this out!

Yes! It works! Oh man, are you feeling like a JavaScript class pro or what? Now let's talk about something kinda weird:
destructuring!
Chapter 11: Destructuring

Next, we're going to talk about two kinda weird things. At first, neither or these will seem all that useful. But even if that were
true, you're going to start to see them in use, even in PHP! So we need to understand them!

Destructuring
The first has a cool name: destructuring! In RepLogApp , find _addRow() . To start, just dump the repLog variable:

 216 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 166
167 _addRow(repLog) {
168 console.log(repLog);
 ... lines 169 - 176
177 }
178 }
 ... lines 179 - 214
215 })(window, jQuery, Routing, swal);

Now, refresh! This is called a bunch of times, and each repLog has the same keys: id , itemLabel , links , reps and
totalWeightLifted . Destructuring allows us to do this weird thing: let {id, itemLabel, reps} = repLog . Below, log id , itemLabel and
reps :

 217 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 166
167 _addRow(repLog) {
168 let {id, itemLabel, reps} = repLog;
169 console.log(id, itemLabel, reps);
 ... lines 170 - 177
178 }
179 }
 ... lines 180 - 215
216 })(window, jQuery, Routing, swal);

Yep, this weird line is actually going to create three new variables - id , itemLabel , and reps - set to the values of the id ,
itemLabel and reps keys in repLog .

Let's see it in action: refresh! Got it! This is called destructuring, and you can even do it with arrays, which looks even stranger.
In that case, the variables are assigned by position, instead of by name. Oh, and side-note, PHP7 introduced destructuring - so
this exists in PHP!

Destructuring & Default Values


What if we try to create a variable that does not match a key, like, I don't know, totallyMadeUpKey :
 217 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 166
167 _addRow(repLog) {
168 let {id, itemLabel, reps, totallyMadeUpKey} = repLog;
169 console.log(id, itemLabel, reps, totallyMadeUpKey);
 ... lines 170 - 177
178 }
179 }
 ... lines 180 - 215
216 })(window, jQuery, Routing, swal);

Try to print that. What do you think will happen?

It's not an error: it just prints as undefined. So destructuring is friendly: if something goes wrong, it doesn't kill your code: it just
assigns undefined. If you think this might be possible, you can give that variable a default, like whatever :

 217 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 166
167 _addRow(repLog) {
168 let {id, itemLabel, reps, totallyMadeUpKey = 'whatever!'} = repLog;
169 console.log(id, itemLabel, reps, totallyMadeUpKey);
 ... lines 170 - 177
178 }
179 }
 ... lines 180 - 215
216 })(window, jQuery, Routing, swal);

Now, if the key doesn't exist, it'll get set to whatever .

So, this is destructuring. It may or may not be useful to you, but you will see it! Don't let it surprise you!
Chapter 12: The... Spread Operator

The second thing weird thing we need to talk about is much more important. It's called the spread operator. Open up play.js
and clear everything out. Create a new function called printThreeThings(thing1, thing2, thing3) . Inside, console.log() our three
things:

 8 lines play.js 
1 let printThreeThings = function(thing1, thing2, thing3) {
2 console.log(thing1, thing2, thing3);
3 };
 ... lines 4 - 8

Easy enough!

Below that, create a new array called yummyThings , which of course naturally will include pizza, gelato, and sushi:

 8 lines play.js 
1 let printThreeThings = function(thing1, thing2, thing3) {
2 console.log(thing1, thing2, thing3);
3 };
4
5 let yummyThings = ['pizza', 'gelato', 'sushi'];
 ... lines 6 - 8

All delicious, but maybe not if you eat them all at the same time.

Here's the question:

How can I pass the three yummy things as the first, second, and third argument to printThreeThings() ?

Well, we could say printThreeThings() with yummyThings[0] , yummyThings[1] and yummyThings[2] . But! Not cool enough! In
ES2015, you can use the spread operator: printThreeThings(...yummyThings) :

8 lines play.js 
1 let printThreeThings = function(thing1, thing2, thing3) {
2 console.log(thing1, thing2, thing3);
3 };
4
5 let yummyThings = ['pizza', 'gelato', 'sushi'];
6
7 printThreeThings(...yummyThings);

Woh.

If we try that, it prints our three things! That's crazy! The ... is called the "spread operator": it's always ... and then your array.
When you do this, it's almost as if somebody went through and literally just wrote out all of the items in your array manually,
instead of us doing it by hand.

But of course, there are a lot of yummy things in the world, and since I'm from the US, my yummyThings should probably have a
cheeseburger:
8 lines play.js 
1 let printThreeThings = function(thing1, thing2, thing3) {
2 console.log(thing1, thing2, thing3);
3 };
4
5 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
6
7 printThreeThings(...yummyThings);

What will happen now? The array has 4 things, but we only have 3 arguments? Let's find out! Run the script!

It's the same result! Thanks to the spread operator, pizza is passed as the first argument, then gelato , sushi and finally,
cheeseburger is passed as the 4th argument. Since the function doesn't have a 4th argument, it's just ignored!

Spread Operator as an Array Merge


Ok great! But you're probably asking yourself: how could this ever be useful in any real world scenario?

Dang, good question. Actually, there are two really great use-cases. One is in ReactJS. I won't talk about it now... just trust me!
The second deals with merging arrays together.

Suppose we need to create another array called greatThings . And we decide that swimming is super great, and sunsets are the
best. They are the best. We also decide that anything "yummy" must also be great, so I want to add all four of these
yummyThings into the greatThings array. In PHP, we might use array_merge() . In JavaScript, one option is the spread operator.
Add a comma - as if we were going to add another entry - and then say ...yummyThings . We could even keep going and add
something else great, like New Orleans . Because New Orleans is a really great place:

 6 lines play.js 
1 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
2
3 let greatThings = ['swimming', 'sunsets', ...yummyThings, 'New Orleans'];
 ... lines 4 - 6

 Tip

There are often many ways to do the same thing in JavaScript, especially with arrays and objects. In this case,
greatThings.concat(yummyThings) is also an option.

Ok, console.log(greatThings) to see if it works!

6 lines play.js 
1 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
2
3 let greatThings = ['swimming', 'sunsets', ...yummyThings, 'New Orleans'];
4
5 console.log(greatThings);

It does: swimming , sunset , 4 yummy things, and New Orleans at the bottom.

Spread Operator for Creating a new Array


But the spread operator has one more cool trick. At the bottom, add another variable: let copyOfGreatThings = greatThings :

 10 lines play.js 
1 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
2
3 let greatThings = ['swimming', 'sunsets', ...yummyThings, 'New Orleans'];
4
5 let copyOfGreatThings = greatThings;
 ... lines 6 - 10

Now, add something else to this new variable: use copyOfGreatThings.push() to add something that we all know is great:
summer :

 10 lines play.js 
1 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
2
3 let greatThings = ['swimming', 'sunsets', ...yummyThings, 'New Orleans'];
4
5 let copyOfGreatThings = greatThings;
6 copyOfGreatThings.push('summer');
 ... lines 7 - 10

At the bottom, console.log(copyOfGreatThings) :

10 lines play.js 
1 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
2
3 let greatThings = ['swimming', 'sunsets', ...yummyThings, 'New Orleans'];
4
5 let copyOfGreatThings = greatThings;
6 copyOfGreatThings.push('summer');
7
8 console.log(greatThings);
9 console.log(copyOfGreatThings);

Here's the question: we know summer now lives in copyOfGreatThings() . But does it also now live inside of greatThings ? Try it!
It does! Summer lives in both arrays! And this makes sense: arrays are objects in JavaScript, and just like in PHP, objects are
passed by reference. In reality, greatThings and copyOfGreatThings are identical: they both point to the same array in memory.

As we get more advanced, especially in React.js, the idea of not mutating objects will become increasingly important. What I
mean is, there will be times when it wil be really important to copy an object, instead of modifying the original.

So how could we make copyOfGreatThings a true copy? The spread operator has an answer: re-assign copyOfGreatThings to
[...greatThings] :

 10 lines play.js 
1 let yummyThings = ['pizza', 'gelato', 'sushi', 'cheeseburger'];
2
3 let greatThings = ['swimming', 'sunsets', ...yummyThings, 'New Orleans'];
4
5 let copyOfGreatThings = [...greatThings];
 ... lines 6 - 10

And that is it! This will create a new array, and then put each item from greatThings into it, one-by-one.

Try it! Yes! We can see summer in the copy, but we did not modify the original array.

Ok, let's move on something that's instantly useful: template strings!


Chapter 13: Template Strings

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:

 5 lines play.js 
1 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 :

 5 lines play.js 
1 const favoriteFood = 'gelato';
2 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:

5 lines play.js 
1 const favoriteFood = 'gelato';
2 const iLoveFood = 'The year is '+(new Date()).getFullYear()+' and my favorite food is '+favoriteFood;
3
4 console.log(iLoveFood);

Now, go enjoy our finished work.

ES2015: Nicer Strings!


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!

5 lines play.js 
1 const favoriteFood = 'gelato';
2 const iLoveFood = `The year is ${(new Date()).getFullYear()} and my favorite food is ${favoriteFood}`;
3
4 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.

Template Strings and Line Breaks


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:
 83 lines app/Resources/views/lift/index.html.twig 
 ... lines 1 - 53
54 {% block javascripts %}
 ... lines 55 - 66
67 <script type="text/template" id="js-rep-log-row-template">
68 <tr data-weight="<%= totalWeightLifted %>">
69 <td><%= itemLabel %></td>
70 <td><%= reps %></td>
71 <td><%= totalWeightLifted %></td>
72 <td>
73 <a href="#"
74 class="js-delete-rep-log"
75 data-url="<%= links._self %>"
76 >
77 <span class="fa fa-trash"></span>
78 </a>
79 </td>
80 </tr>
81 </script>
82 {% endblock %}

Then, in _addRow() , we found that script element by its id and fetched the string:

 218 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4 class RepLogApp {
 ... lines 5 - 166
167 _addRow(repLog) {
 ... lines 168 - 171
172 const tplText = $('#js-rep-log-row-template').html();
173 const tpl = _.template(tplText);
174
175 const html = tpl(repLog);
176 this.$wrapper.find('tbody').append($.parseHTML(html));
177
178 this.updateTotalWeightLifted();
179 }
180 }
 ... lines 181 - 216
217 })(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:
 234 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 215
216 const rowTemplate = '
217 <tr data-weight="<%= totalWeightLifted %>">
218 <td><%= itemLabel %></td>
219 <td><%= reps %></td>
220 <td><%= totalWeightLifted %></td>
221 <td>
222 <a href="#"
223 class="js-delete-rep-log"
224 data-url="<%= links._self %>"
225 >
226 <span class="fa fa-trash"></span>
227 </a>
228 </td>
229 </tr>
230 ';
 ... lines 231 - 232
233 })(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:

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 216
217 const rowTemplate = `
218 <tr data-weight="<%= totalWeightLifted %>">
219 <td><%= itemLabel %></td>
220 <td><%= reps %></td>
221 <td><%= totalWeightLifted %></td>
222 <td>
223 <a href="#"
224 class="js-delete-rep-log"
225 data-url="<%= links._self %>"
226 >
227 <span class="fa fa-trash"></span>
228 </a>
229 </td>
230 </tr>
231 `;
 ... lines 232 - 233
234 })(window, jQuery, Routing, swal);

To celebrate, remove the template from Twig:


 67 lines app/Resources/views/lift/index.html.twig 
 ... lines 1 - 53
54 {% block javascripts %}
55 {{ parent() }}
56
57 <script src="https://cdn.jsdelivr.net/sweetalert2/6.1.0/sweetalert2.min.js"></script>
58 <script src="{{ asset('assets/js/RepLogApp.js') }}"></script>
59
60 <script>
61 $(document).ready(function() {
62 var $wrapper = $('.js-rep-log-table');
63 var repLogApp = new RepLogApp($wrapper);
64 });
65 </script>
66 {% endblock %}

Now, we can simplify things! Up in _addRow() , set tplText to simply equal rowTemplate :

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 class RepLogApp {
 ... lines 6 - 167
168 _addRow(repLog) {
 ... lines 169 - 172
173 const tplText = rowTemplate;
 ... lines 174 - 179
180 }
181 }
 ... lines 182 - 216
217 const rowTemplate = `
218 <tr data-weight="<%= totalWeightLifted %>">
219 <td><%= itemLabel %></td>
220 <td><%= reps %></td>
221 <td><%= totalWeightLifted %></td>
222 <td>
223 <a href="#"
224 class="js-delete-rep-log"
225 data-url="<%= links._self %>"
226 >
227 <span class="fa fa-trash"></span>
228 </a>
229 </td>
230 </tr>
231 `;
232
233 window.RepLogApp = RepLogApp;
234 })(window, jQuery, Routing, swal);

Using Template Strings as a Template Engine


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:
 235 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 216
217 const rowTemplate = `
218 <tr data-weight="${repLog.totalWeightLifted}">
 ... lines 219 - 229
230 </tr>
231 `;
 ... lines 232 - 233
234 })(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!

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 216
217 const rowTemplate = `
218 <tr data-weight="${repLog.totalWeightLifted}">
219 <td>${repLog.itemLabel}</td>
220 <td>${repLog.reps}</td>
221 <td>${repLog.totalWeightLifted}</td>
222 <td>
223 <a href="#"
224 class="js-delete-rep-log"
225 data-url="${repLog.links._self}"
226 >
227 <span class="fa fa-trash"></span>
228 </a>
229 </td>
230 </tr>
231 `;
 ... lines 232 - 233
234 })(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

Turning our Template into a Function


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:
 232 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 213
214 const rowTemplate = (repLog) => `
215 <tr data-weight="${repLog.totalWeightLifted}">
216 <td>${repLog.itemLabel}</td>
217 <td>${repLog.reps}</td>
218 <td>${repLog.totalWeightLifted}</td>
219 <td>
220 <a href="#"
221 class="js-delete-rep-log"
222 data-url="${repLog.links._self}"
223 >
224 <span class="fa fa-trash"></span>
225 </a>
226 </td>
227 </tr>
228 `;
 ... lines 229 - 230
231 })(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) :

 232 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 class RepLogApp {
 ... lines 6 - 167
168 _addRow(repLog) {
 ... lines 169 - 172
173 const html = rowTemplate(repLog);
174 this.$wrapper.find('tbody').append($.parseHTML(html));
175
176 this.updateTotalWeightLifted();
177 }
178 }
 ... lines 179 - 230
231 })(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.

Tagged Template Strings


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:
 238 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 213
214 function upper(template, ...expressions) {
215 return template.reduce((accumulator, part, i) => {
216 return accumulator + (expressions[i - 1].toUpperCase ? expressions[i - 1].toUpperCase() : expressions[i - 1]) + part
217 })
218 }
 ... lines 219 - 236
237 })(window, jQuery, Routing, swal);

Just follow me on this: right before the tick that starts the template string, add the function's name: upper :

 238 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 213
214 function upper(template, ...expressions) {
215 return template.reduce((accumulator, part, i) => {
216 return accumulator + (expressions[i - 1].toUpperCase ? expressions[i - 1].toUpperCase() : expressions[i - 1]) + part
217 })
218 }
219
220 const rowTemplate = (repLog) => upper`
 ... lines 221 - 233
234 `;
 ... lines 235 - 236
237 })(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!

 238 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 213
214 function upper(template, ...expressions) {
 ... lines 215 - 217
218 }
 ... lines 219 - 236
237 })(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:


 232 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 213
214 const rowTemplate = (repLog) => `
 ... lines 215 - 227
228 `;
 ... lines 229 - 230
231 })(window, jQuery, Routing, swal);
Chapter 14: The for of Loop

One of the crazy things about JavaScript... is that there's not one good way to loop over a collection! In PHP, we have foreach ,
and it works perfectly. But in JavaScript, you need to create an ugly custom for loop. Well actually, there is a .forEach()
function, but it only works on arrays, not other loopable things, like the Set object we'll talk about later. And, with .forEach() ,
there is no break if you want to exit the loop early.

That's why we've been using jQuery's $.each() . But guess what? ES2015 fixes this, finally. Introducing, the for of loop!

It looks like this: for (let repLog of data.items) :

 233 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 37
38 loadRepLogs() {
39 $.ajax({
 ... line 40
41 }).then(data => {
42 for (let repLog of data.items) {
43 this._addRow(repLog);
44 }
45 })
46 }
 ... lines 47 - 230
231 window.RepLogApp = RepLogApp;
 ... lines 232 - 233

And it's pretty easy to follow: repLog is the new variable inside the loop, and data.items is the thing we want to loop over. We're
no longer passing this an anonymous function, so we can get rid of everything else. That's it. Say hello to your new best friend:
the for of loop.

Let's look for the other $.each() spots and update those too! Instead, say for let fieldData of $form.serializeArray() :

 233 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 class RepLogApp {
 ... lines 6 - 94
95 handleNewFormSubmit(e) {
 ... lines 96 - 100
101 for (let fieldData of $form.serializeArray()) {
102 formData[fieldData.name] = fieldData.value
103 }
 ... lines 104 - 111
112 }
 ... lines 113 - 212
213 }
 ... lines 214 - 231
232 })(window, jQuery, Routing, swal);

Before, the anonymous function received a key and then the fieldData . But, we didn't actually need the key: the $.each()
function just forced us to add it. Now, things are cleaner!

 Tip
Since we are in a for loop now, we need to also update the return statement to be continue .

Make this same change in two more places: for $element of $form.find(':input') . Ah, don't forget your let or var :

 233 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 4
5 class RepLogApp {
 ... lines 6 - 136
137 _mapErrorsToForm(errorData) {
 ... lines 138 - 140
141 for (let element of $form.find(':input')) {
 ... lines 142 - 152
153 }
154 }
 ... lines 155 - 178
179 }
 ... lines 180 - 233

Then, one more below: for let $element of $elements :

 233 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 183
184 class Helper {
 ... lines 185 - 204
205 static _calculateWeights($elements) {
 ... line 206
207 for (let element of $elements) {
 ... line 208
209 }
 ... lines 210 - 211
212 }
213 }
 ... lines 214 - 231
232 })(window, jQuery, Routing, swal);

Oh, and PhpStorm is warning me because I forgot to remove one of my closing parentheses! And, we don't need that
semicolon! Yay!

So, use the for of loop for everything! Well actually, that's not 100% true. for of is perfect when you want to loop over a
collection of items. But, if you want to loop over an associative array... or object, and you need to know the key for each item,
then you'll use for in .

 Tip

Actually, you can use for of with an object, with a clever combination of Object.entries() and array destructuring!

let pets = {
beagle: 'Bark Twain',
poodle: 'Snuffles'
};

for (let [petKey, petName] of Object.entries(pets)) {


console.log(petKey, petName);
}

BUT, the Object.entries() method is still experimental, and may be included in ES2017.

This is the one limitation of for of : it gives you the value of the item you're looping over, but not its key, or index. In fact, if try to
use for of with an object, you'll get an error.
Chapter 15: Map and WeakMap

So far, all the new ES2015 stuff has been new language constructs: new syntaxes and keywords, like let , const and classes!
And that was no accident: these are the most important things to understand.

But ES2015 comes packed with other new features, like new functions and new objects. And mostly, those are easy enough to
understand: when you see an object or function you don't recognize, look it up, see how it works... and keep going!

The Map Object


But, there is one set of objects... pun intended... that I do want to talk about. They are, Map , WeakMap and... Set !

Head back into play.js . Let's experiment with Map first.

Right now, when you need an associative array, you just create an object: foods = {} and start adding delicious things to it:
foods.italian = 'gelato' , foods.mexican = 'torta' and foods.canadian = 'poutine' . Poutine is super delicious:

 7 lines play.js 
1 let foods = {};
2 foods.italian = 'gelato';
3 foods.mexican = 'tortas';
4 foods.canadian = 'poutine';
 ... lines 5 - 7

At the bottom, of course, we can log foods.italian :

7 lines play.js 
1 let foods = {};
2 foods.italian = 'gelato';
3 foods.mexican = 'tortas';
4 foods.canadian = 'poutine';
5
6 console.log(foods.italian);

And no surprise, our console tells us we should eat gelato. Good idea!

In ES2015, we now have a new tool: instead of creating a simple object, we can create a new Map object. The syntax is
slightly different: instead of foods.italian = 'gelato' , use foods.set('italian', 'gelato') :

 7 lines play.js 
1 let foods = new Map();
2 foods.set('italian', 'gelato');
 ... lines 3 - 7

Repeat this for the other two keys. And at the bottom, fetch the value with foods.get('italian') :

7 lines play.js 
1 let foods = new Map();
2 foods.set('italian', 'gelato');
3 foods.set('mexican', 'tortas');
4 foods.set('canadian', 'poutine');
5
6 console.log(foods.get('italian'));

Simple and beautiful! And it works exactly like before!

Great! So... we have a new Map object... and it's a different way to create an associative array. But why would we use it?
Because it comes with some nice helper methods! For example, we can say foods.has('french') :

10 lines play.js 
1 let foods = new Map();
2 foods.set('italian', 'gelato');
3 foods.set('mexican', 'tortas');
4 foods.set('canadian', 'poutine');
5
6 console.log(
7 foods.get('italian'),
8 foods.has('french')
9 );

And that returns false . Bummer for us.

It wasn't too difficult to check if a key existed before, but this feels clean.

Map with Non-String Keys


Map has one other advantage... which is kind of crazy: you can use non-string keys!

Try this: create a new variable: let southernUSStates set to an array of Tennessee , Kentucky , and Texas :

 13 lines play.js 
 ... lines 1 - 5
6 let southernUsStates = ['Tennessee', 'Kentucky', 'Texas'];
 ... lines 7 - 13

Now we can say foods.set(southernUSStates) and set that to hot chicken :

 13 lines play.js 
 ... lines 1 - 5
6 let southernUsStates = ['Tennessee', 'Kentucky', 'Texas'];
7 foods.set(southernUsStates, 'hot chicken');
 ... lines 8 - 13

Yes, the key is actually an object. And that's no problem!

Important side note: hot chicken is really only something you should eat in Tennessee, but for this example, I needed to include
a few other states. In Texas, you should eat Brisket.

Anyways, at the bottom, use foods.get(southernUSStates) to fetch out that value:

 13 lines play.js 
 ... lines 1 - 5
6 let southernUsStates = ['Tennessee', 'Kentucky', 'Texas'];
7 foods.set(southernUsStates, 'hot chicken');
8
9 console.log(
10 foods.get('italian'),
11 foods.get(southernUsStates)
12 );

And it works just like we want!

If you're wondering when this would be useful... stay tuned. Oh, and there's one other property you should definitely know
about: foods.size :
 14 lines play.js 
 ... lines 1 - 5
6 let southernUsStates = ['Tennessee', 'Kentucky', 'Texas'];
7 foods.set(southernUsStates, 'hot chicken');
8
9 console.log(
10 foods.get('italian'),
11 foods.get(southernUsStates),
12 foods.size
13 );

That will print 4. Say hello to the new Map object!

 Tip

You can also loop over a Map using our new friend - the for of loop. You can loop over the values or the keys!

// loop over the keys *and* values


for (let [countryKey, food] of foods.entries()) {
console.log(countryKey, food); // e.g. italian gelato
}

// loop over the keys (e.g. italian)


for (let countryKey of foods.keys()) {
console.log(countryKey);
}

Behind the scenes, the last example uses destructuring to assign each returned by entries() to the countryKey and food
variables. It's all coming together!

Introducing WeakMap... a worse Map?


ES2015 also gives us a very similar new object: WeakMap :

 14 lines play.js 
1 let foods = new WeakMap();
 ... lines 2 - 14

And this is where things get a little nuts. Why do we have a Map and a WeakMap ?

Let's find out! First try to run our code with WeakMap .

Woh, it explodes!

Invalid value used as week map key

Map and WeakMap are basically the same... except WeakMap has an extra requirement: its keys must be objects. So yes, for
now, it seems like WeakMap is just a worse version of Map .

Turn each key into an array, which is an object. At the bottom, use foods.get() and pass it the italian array:

 14 lines play.js 
1 let foods = new WeakMap();
2 foods.set(['italian'], 'gelato');
3 foods.set(['mexican'], 'tortas');
4 foods.set(['canadian'], 'poutine');
 ... lines 5 - 8
9 console.log(
10 foods.get(['italian']),
 ... lines 11 - 12
13 );
Now when I run it, it works fine. Wait, or, does it?

Two interesting things: this prints undefined , hot chicken , undefined . First, even though the ['italian'] array in get() is equal to
the ['italian'] array used in set, they are not the same object in memory. These are two distinct objects, so it looks like a different
key to WeakMap . That's why it prints undefined .

Second, with WeakMap , you can't call foods.size . That's just not something that works with WeakMap .

WeakMap and Garbage Collection


Let me show you one other crazy thing, which will start to show you the purpose of WeakMap . After we set the
southernUSStates onto the array, I'm going to set southernUSStates to null:

 15 lines play.js 
1 let foods = new WeakMap();
2 foods.set(['italian'], 'gelato');
3 foods.set(['mexican'], 'tortas');
4 foods.set(['canadian'], 'poutine');
5
6 let southernUsStates = ['Tennessee', 'Kentucky', 'Texas'];
7 foods.set(southernUsStates, 'hot chicken');
8 southernUsStates = null;
 ... lines 9 - 15

When you try it now, this of course prints "undefined". That makes sense: we're now passing null to the get() function.

But what you can't see is that the southernUSStates object no longer exists... anywhere in memory!

Why? In JavaScript, if you have a variable that isn't referenced by anything else anymore, like southernUSStates , it's eligible to
be removed by JavaScript's garbage collection. The same thing happens in PHP.

But normally, because we set southernUSStates as a key on WeakMap , this reference to southernUSStates would prevent that
garbage collection. That's true with Map , but not WeakMap : it does not prevent garbage collection. In other words, even though
southernUSStates is still on our WeakMap , since it's not being referenced anywhere else, it gets removed from memory thanks
to garbage collection.

But, really, how often do you need to worry about garbage collection when building a web app? Probably not very often. So, at
this point, you should just use Map everywhere: it's easier and has more features.

And that's true! Except for one special, fascinating, nerdy WeakMap use-case. Let's learn about it!
Chapter 16: Private Variables & WeakMap

To see a real-world WeakMap use-case, go back into RepLogApp and scroll to the top. Remember, this file holds two classes:
RepLogApp and, at the bottom, Helper :

 233 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 class RepLogApp {
 ... lines 6 - 178
179 }
180
181 /**
182 * A "private" object
183 */
184 class Helper {
 ... lines 185 - 212
213 }
 ... lines 214 - 231
232 })(window, jQuery, Routing, swal);

The purpose of Helper is to be a private object that we can only reference from inside of this self-executing function.

Making Helper a Private Object


But check out the constructor for RepLogApp :

 233 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 class RepLogApp {
6 constructor($wrapper) {
 ... line 7
8 this.helper = new Helper(this.$wrapper);
 ... lines 9 - 26
27 }
 ... lines 28 - 178
179 }
 ... lines 180 - 231
232 })(window, jQuery, Routing, swal);

We set Helper onto a helper property. We do this so that we can use it later, inside of updateTotalWeightLifted() . Here's the
problem: the helper property is not private. I mean, inside of our template, if we wanted, we could say:
repLogApp.helper.calculateTotalWeight() .

Dang! We went to all of that trouble to create a private Helper object... and it's not actually private! Lame!

How can we fix this? Here's an idea: above the class, create a new HelperInstance variable set to null :
 235 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstance = null;
6
7 class RepLogApp {
 ... lines 8 - 180
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

Then, instead of setting the new Helper onto a property - which is accessible from outside, say: HelperInstance = new Helper() :

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstance = null;
6
7 class RepLogApp {
8 constructor($wrapper) {
9 this.$wrapper = $wrapper;
10 HelperInstance = new Helper(this.$wrapper);
 ... lines 11 - 28
29 }
 ... lines 30 - 180
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

And that's it! The HelperInstance variable is not available outside our self-executing function. And of course down below, in
updateTotalWeightLifted() , the code will now read: HelperInstance.getTotalWeightString() :

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
 ... lines 8 - 49
50 updateTotalWeightLifted() {
51 this.$wrapper.find('.js-total-weight').html(
52 HelperInstance.getTotalWeightString()
53 );
54 }
 ... lines 55 - 180
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

And just like that, we've made Helper truly private.

Multiple Instances with Map!


Well... you might already see the problem! Even though we're not doing it here, it is legal to create multiple RepLogApp objects.
And if we did create two RepLogApp objects, well the second would replace the HelperInstance from the first! We can only ever
have one HelperInstance ... even though we may have multiple RepLogApp objects. Bad design Ryan!

Ok, so why not use our cool new Map object to store a collection of Helper objects? let HelperInstances = new Map() :
 235 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstances = new Map();
 ... lines 6 - 233
234 })(window, jQuery, Routing, swal);

In the constructor() , set the new object into that map: HelperInstances.set() ... and for the key - this may look a little weird - use
this :

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstances = new Map();
6
7 class RepLogApp {
8 constructor($wrapper) {
9 this.$wrapper = $wrapper;
10 HelperInstances.set(this, new Helper(this.$wrapper));
 ... lines 11 - 28
29 }
 ... lines 30 - 180
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

In other words, we key this HelperInstance to ourselves, our instance. That means that later, to use it, say
HelperInstances.get(this).getTotalWeightString() :

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
 ... lines 8 - 49
50 updateTotalWeightLifted() {
51 this.$wrapper.find('.js-total-weight').html(
52 HelperInstances.get(this).getTotalWeightString()
53 );
54 }
 ... lines 55 - 180
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

This is awesome! Helper is still private, but now each RepLogApp instance will have its own instance of Helper in the Map .

Just to prove this is not breaking everything, refresh! Woohoo!

Playing with Garbage Collection


Time for an experiment! Go all the way to the bottom of the file. Create a new RepLogApp object... and just pass in the body
tag. Copy this and repeat it three other times:
 242 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 233
234 new RepLogApp($('body'));
235 new RepLogApp($('body'));
236 new RepLogApp($('body'));
237 new RepLogApp($('body'));
 ... lines 238 - 240
241 })(window, jQuery, Routing, swal);

Notice that these are not being used: I'm not setting them to a variable. In other words, they are created, and then they're gone:
no longer referenced by anything. Below that - and this won't make sense yet, call setTimeout() , pass it an arrow function, and
inside, console.log(HelperInstances) . Set that to run five seconds after we load the page:

 242 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 233
234 new RepLogApp($('body'));
235 new RepLogApp($('body'));
236 new RepLogApp($('body'));
237 new RepLogApp($('body'));
238 console.log(HelperInstances);
 ... lines 239 - 240
241 })(window, jQuery, Routing, swal);

Mysterious!?

Ok, refresh! And then wait a few seconds... we should see the Map printed with five Helper objects inside. Yep, we do! One
Helper for each RepLogApp we created.

But now, back in RepLogApp , after we set the HelperInstance , simply return:

 242 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstances = new Map();
6
7 class RepLogApp {
8 constructor($wrapper) {
9 this.$wrapper = $wrapper;
10 HelperInstances.set(this, new Helper($wrapper));
11 return;
 ... lines 12 - 29
30 }
 ... lines 31 - 181
182 }
 ... lines 183 - 240
241 })(window, jQuery, Routing, swal);

This is a temporary hack to show off garbage collection. Now that we're returning immediately, when we create a new
RepLogApp object, it's not attaching any listeners or adding itself as a reference to anything in the code. In other words, this
object is not attached or referenced anywhere in memory. Because of that, RepLogApp objects - and their Helper objects -
should be eligible for garbage collection.

Now, garbage collection isn't an instant process - it takes places at intervals, and it's up to your JavaScript engine to worry
about that. But if you're using Chrome, you can force garbage collection! On the timeline tab, you should see a little garbage
icon. Try this: refresh! Quickly click the "collect garbage" button, and then see what prints in the console.
Ok, so HelperInstances still has 5 objects inside. In other words, the Helper objects were not garbage collected. Why? Because
they are still being referenced in the code... by the Map itself!

Now, change the Map to a WeakMap :

 242 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstances = new WeakMap();
 ... lines 6 - 240
241 })(window, jQuery, Routing, swal);

Go back and repeat the dance: refresh, hit the garbage icon, and then go to the console. Woh! Check this out! The WeakMap is
empty. Remember, this is its superpower! Since none of the RepLogApp objects are being referenced in memory anymore, both
those and their Helper instances are eligible for garbage collection. When you use Map , it prevents this: simply being inside of
the Map counts as a reference. With WeakMap that doesn't happen.

Ok, I know, this was still pretty darn advanced. So you may or may not have this use case. But this is when you will see
WeakMap used instead of Map . For us it means we should use Map in normal situations... and WeakMap only if we find
ourselves with this problem.

Get rid of all our debug code:

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
4
5 let HelperInstances = new WeakMap();
6
7 class RepLogApp {
8 constructor($wrapper) {
 ... lines 9 - 28
29 }
 ... lines 30 - 180
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

And our page is happy again!


Chapter 17: Array, Set and ES2016

The Map object is perfect for maps, or associative arrays as we call them in the PHP biz. But what about true, indexed arrays?
Well actually, JavaScript has always had a great way to handle these - it's not new! It's the Array object.

Well, the Array object isn't new, but it does have a new trick. Let's check out an example: when the page loads, we call
loadRepLogs() :

 235 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
8 constructor($wrapper) {
 ... lines 9 - 11
12 this.loadRepLogs();
 ... lines 13 - 28
29 }
 ... lines 30 - 39
40 loadRepLogs() {
41 $.ajax({
42 url: Routing.generate('rep_log_list'),
43 }).then(data => {
44 for (let repLog of data.items) {
45 this._addRow(repLog);
46 }
47 })
48 }
 ... lines 49 - 170
171 _addRow(repLog) {
 ... lines 172 - 175
176 const html = rowTemplate(repLog);
177 this.$wrapper.find('tbody').append($.parseHTML(html));
178
179 this.updateTotalWeightLifted();
180 }
181 }
 ... lines 182 - 233
234 })(window, jQuery, Routing, swal);

This fetches an array of repLog data via AJAX and then calls _addRow() on each to add the <tr> elements to the table.

But once we add the table rows... we don't actually store those repLog objects anywhere. Yep, we use them to build the page...
then say: Adios!

Now, I do want to start storing this data on my object, and you'll see why in a minute. Up in the constructor , create a repLogs
property set to new Array() :
 239 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
8 constructor($wrapper) {
 ... line 9
10 this.repLogs = new Array();
 ... lines 11 - 30
31 }
 ... lines 32 - 184
185 }
 ... lines 186 - 237
238 })(window, jQuery, Routing, swal);

If you've never seen that Array object before... there's a reason - stay tuned! Then, down in _addRow() , say this.repLogs() -
which is the Array object - this.repLogs.push(repLog) :

 239 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
 ... lines 8 - 173
174 _addRow(repLog) {
175 this.repLogs.push(repLog);
 ... lines 176 - 183
184 }
185 }
 ... lines 186 - 237
238 })(window, jQuery, Routing, swal);

Back up in loadRepLogs() , after the for loop, let's see how this looks: console.log(this.repLogs) . Oh, and let's also use one of its
helper methods: this.repLogs.includes(data.items[0]) :

 239 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
 ... lines 8 - 41
42 loadRepLogs() {
43 $.ajax({
 ... line 44
45 }).then(data => {
 ... lines 46 - 48
49 console.log(this.repLogs, this.repLogs.includes(data.items[0]));
50 })
51 }
 ... lines 52 - 184
185 }
 ... lines 186 - 237
238 })(window, jQuery, Routing, swal);

Obviously, this item should have been added to the Array !

Refresh! Yea! We see the fancy Array and the word true . Awesome!

But hold on! The Array object may not be new, but the includes() function is new. In fact, it's really new - it wasn't added in
ES2015, it was added in ES2016! ES2015 came with a ton of new features. And now, new ECMAScript releases happen
yearly, but with many fewer new things. The Array ' includes() function is one of those few things in ES2016. Cool!
Oh, and by the way, you don't typically say new Array() ... and PHPStorm is yelling at us! In the wild, you just use [] :

 239 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
8 constructor($wrapper) {
 ... line 9
10 this.repLogs = [];
 ... lines 11 - 30
31 }
 ... lines 32 - 218
219 }
 ... lines 220 - 237
238 })(window, jQuery, Routing, swal);

That's right, when you create an array in JavaScript, it's actually this Array object.

Calculating the Total Weight


But... why are we keeping track of the repLogs ? Because now, we can more easily calculate the total weight. Before, we
passed the Helper object the $wrapper element so that it could find all the tr elements and read the weight from them. We can
simplify this! Instead, pass it our Array : this.repLogs :

 238 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
8 constructor($wrapper) {
 ... line 9
10 this.repLogs = [];
11
12 HelperInstances.set(this, new Helper(this.repLogs));
 ... lines 13 - 30
31 }
 ... lines 32 - 183
184 }
 ... lines 185 - 236
237 })(window, jQuery, Routing, swal);

At the bottom of this file, change the constructor() for Helper to have a repLogs argument. Set that on a repLogs property:

 238 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 185
186 /**
187 * A "private" object
188 */
189 class Helper {
190 constructor(repLogs) {
191 this.repLogs = repLogs;
192 }
 ... lines 193 - 217
218 }
 ... lines 219 - 236
237 })(window, jQuery, Routing, swal);

Below in calculateTotalWeight() , instead of using the $wrapper to find all the tr elements, just pass this.repLogs to the static
function. Inside of that, update the argument to repLogs :

 238 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 185
186 /**
187 * A "private" object
188 */
189 class Helper {
 ... lines 190 - 193
194 calculateTotalWeight() {
195 return Helper._calculateWeights(
196 this.repLogs
197 );
198 }
 ... lines 199 - 209
210 static _calculateWeights(repLogs) {
 ... lines 211 - 216
217 }
218 }
 ... lines 219 - 236
237 })(window, jQuery, Routing, swal);

Previously, _calculateWeights() would loop over the $elements and read the data-weight attribute on each. Now, loop over
repLog of repLogs . Inside, set totalWeight += repLog.totalWeightLifted :

 238 lines web/assets/js/RepLogApp.js 


 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 185
186 /**
187 * A "private" object
188 */
189 class Helper {
 ... lines 190 - 209
210 static _calculateWeights(repLogs) {
211 let totalWeight = 0;
212 for (let repLog of repLogs) {
213 totalWeight += repLog.totalWeightLifted;
214 }
215
216 return totalWeight;
217 }
218 }
 ... lines 219 - 236
237 })(window, jQuery, Routing, swal);

It's nice to calculate the total weight from our source data, rather than reading it from somewhere on the DOM.

Okay! Try that out! The table still loads... and the total still prints!

 Tip

Actually, we made a mistake! When you delete a rep log, the total weight will no longer update! That's because we now
need to remove the deleted repLog from the this.repLogs array.

No problem! The fix is kinda cool: it involves adding a reference to the $row element: the index on the this.repLogs array
that the row corresponds to. This follows a pattern that's somewhat similar to what you'll see in ReactJS.
 249 lines web/assets/js/RepLogApp.js 
 ... lines 1 - 2
3 (function(window, $, Routing, swal) {
 ... lines 4 - 6
7 class RepLogApp {
 ... lines 8 - 73
74 _deleteRepLog($link) {
 ... lines 75 - 83
84 return $.ajax({
 ... lines 85 - 86
87 }).then(() => {
88 $row.fadeOut('normal', () => {
89 // we need to remove the repLog from this.repLogs
90 // the "key" is the index to this repLog on this.repLogs
91 this.repLogs.splice(
92 $row.data('key'),
93 1
94 );
95
96 $row.remove();
97
98 this.updateTotalWeightLifted();
99 });
100 })
101 }
 ... lines 102 - 180
181 _addRow(repLog) {
182 this.repLogs.push(repLog);
 ... lines 183 - 186
187 const html = rowTemplate(repLog);
188 const $row = $($.parseHTML(html));
189 // store the repLogs index
190 $row.data('key', this.repLogs.length - 1);
191 this.$wrapper.find('tbody').append($row);
192
193 this.updateTotalWeightLifted();
194 }
195 }
 ... lines 196 - 247
248 })(window, jQuery, Routing, swal);

Introducing Set
But, ES2015 added one more new object that's related to all of this: Set . It's a lot like Array : it holds items... but with one
important difference.

Open up play.js and set foods to an array:

 7 lines play.js 
1 let foods = [];
 ... lines 2 - 7

Let's add gelato to the array and tortas . Clear everything else out:

 7 lines play.js 
1 let foods = [];
2 foods.push('gelato');
3 foods.push('tortas');
 ... lines 4 - 7
And ya know what? Gelato is so good, we should add it again. At the bottom, log foods :

7 lines play.js 
1 let foods = [];
2 foods.push('gelato');
3 foods.push('tortas');
4 foods.push('gelato');
5
6 console.log(foods);

When you run the script, there are no surprises: gelato , tortas , gelato .

But now, change the array to be a new Set() . To add items to a Set , you'll use add() instead of push() - but it's the same idea:

7 lines play.js 
1 let foods = new Set();
2 foods.add('gelato');
3 foods.add('tortas');
4 foods.add('gelato');
5
6 console.log(foods);

Try the script now.

Woh! Just two items! That's the key difference between Array and Set : Set should be used when you need a unique
collection of items. It automatically makes sure that duplicates aren't added.

Oh, and there is also a WeakSet , which has the same super powers of WeakMap - all that garbage collection stuff. But, I
haven't seen any decent use-case for it. Just use Set ... or Array if values don't need to be unique.
Chapter 18: yarn & npm: Installing Babel

Ok, so do you love ES2015 yet? Man, I sure do. So... let's go use it immediately on our site! Oh wait.... didn't I mention that its
features aren't supported by all browsers? Whoops. Yea, the latest version of Chrome supports everything... but if some of our
users have older browsers, does this mean we can't use ES2015?

npm and yarn: Package Manager Battle!


Nope! You can! Thanks to an amazing tool called Babel. But before we talk about it, we need to do a little bit of installation. In
the Node.js world, the package manager is called npm, and because you already installed Node, you already have it!

But wait! There's a disturbance in the Node.js package manager force. Yes, there is another... package manager... called Yarn!
It's sort of a competitor to npm, but they work almost identically. For example, in PHP, we have a composer.json file. In Node,
we will have a package.json ... and you can use either npm or yarn to read it.

In other words,... you can use npm or yarn : they basically do the same thing. You could even have some people on your team
using npm , and others using yarn .

We're going to use Yarn... because it's a bit more sophisticated. But, that means you need to install it! I'm on a Mac, so I already
installed it via Brew. Check Yarn's Docs for your install details.

Creating a package.json
To use Yarn, we need a package.json file... which we don't have yet! No worries, to create one, run:

$ yarn init

It'll ask a bunch of questions - none are too important - and you can always update your new package.json file by hand later:

9 lines package.json 
1 {
2 "name": "javascript",
3 "version": "1.0.0",
4 "main": "index.js",
5 "repository": "git@github.com:knpuniversity/javascript.git",
6 "author": "Ryan Weaver <ryan@thatsquality.com>",
7 "license": "MIT"
8 }

Awesome!

Installing Babel
Ok, the wonderful tool that will fix all of our browser compatibility problems with ES2015 is called Babel. Google for it to find
babeljs.io .

In a nut shell, Babel reads new JavaScript code, i.e. ES2015 code, and recompiles it to old JavaScript so that all browsers can
understand it. Yea, it literally reads source code and converts it to different source code. It's wild!

Go to Setup. In our case, to see how it works, we're going to use the CLI, which means we will run Babel from the command
line. To install Babel CLI, it wants us to run npm install --save-dev babel-cli .

But don't do it! Since we're using Yarn, run:


$ yarn add babel-cli --dev

That does the same thing, but with more exciting output!

This made a few changes to our project. Most importantly, it added this devDependencies section to package.json with babel-cli
inside:

 12 lines package.json 
1 {
 ... lines 2 - 7
8 "devDependencies": {
9 "babel-cli": "^6.22.2"
10 }
11 }

It also created a yarn.lock file: which works like composer.lock . And finally, the command added a new node_modules/
directory, where it downloaded babel-cli and all of its friends, um, dependencies. That is the vendor/ directory for Node.

Open up your .gitignore file. At the bottom, let's ignore /node_modules/ :

 18 lines .gitignore 
 ... lines 1 - 16
17 /node_modules

We don't need to commit that directory because - thanks to the package.json and yarn.lock files - anyone else can run
yarn install to download everything they need.

Okay, enough setup! Let's use Babel!


Chapter 19: Babel: Transpiling to Old JavaScript

Time to use Babel! How? At your terminal, type

$ ./node_modules/.bin/babel

 Tip

On some systems, you may need to type:

$ node ./node_modules/.bin/babel

That is the path to the executable for Babel. Next, point to our source file: web/assets/js/RepLogApp.js and then pass -o and the
path to where the final, compiled, output file should live: web/assets/dist/RepLogApp.js .

Before you run that, go into web/assets , and create that new dist/ directory. Now, hold your breath and... run that command!

$ ./node_modules/.bin/babel web/assets/js/RepLogApp.js -o web/assets/dist/RepLogApp.js

And boom! Suddenly, we have a new RepLogApp.js file.

Before we look at it, go into index.html.twig and update the script tag to point to the new dist version of RepLogApp.js that Babel
just created:

 67 lines app/Resources/views/lift/index.html.twig 
 ... lines 1 - 53
54 {% block javascripts %}
 ... lines 55 - 57
58 <script src="{{ asset('assets/dist/RepLogApp.js') }}"></script>
 ... lines 59 - 65
66 {% endblock %}

Ok, refresh! It still works!

So what did Babel do? What are the differences between those two files? Let's find out! Open the new file:
 218 lines web/assets/dist/RepLogApp.js 
1 'use strict';
2
3 (function (window, $, Routing, swal) {
4
5 let HelperInstances = new WeakMap();
6
7 class RepLogApp {
8 constructor($wrapper) {
9 this.$wrapper = $wrapper;
10 this.repLogs = new Set();
11
12 HelperInstances.set(this, new Helper(this.repLogs));
13
14 this.loadRepLogs();
15
16 this.$wrapper.on('click', '.js-delete-rep-log', this.handleRepLogDelete.bind(this));
17 this.$wrapper.on('click', 'tbody tr', this.handleRowClick.bind(this));
18 this.$wrapper.on('submit', RepLogApp._selectors.newRepForm, this.handleNewFormSubmit.bind(this));
19 }
 ... lines 20 - 165
166 }
167
168 /**
169 * A "private" object
170 */
171 class Helper {
172 constructor(repLogSet) {
173 this.repLogSet = repLogSet;
174 }
 ... lines 175 - 197
198 }
199
200 const rowTemplate = repLog => `
201 <tr data-weight="${repLog.totalWeightLifted}">
202 <td>${repLog.itemLabel}</td>
203 <td>${repLog.reps}</td>
204 <td>${repLog.totalWeightLifted}</td>
205 <td>
206 <a href="#"
207 class="js-delete-rep-log"
208 data-url="${repLog.links._self}"
209 >
210 <span class="fa fa-trash"></span>
211 </a>
212 </td>
213 </tr>
214 `;
215
216 window.RepLogApp = RepLogApp;
217 })(window, jQuery, Routing, swal);

Hmm, it actually doesn't look any different. And, that's right! To prove it, use the diff utility to compare the files:

$ diff -u web/assets/js/RepLogApp.js web/assets/dist/RepLogApp.js

Wait, so there are some differences... but they're superficial: just a few space differences here and there. Babel did not actually
convert the code to the old JavaScript format! We can still see the arrow functions!
Here's the reason. As crazy as it sounds, by default, Babel does... nothing! Babel is called a transpiler, which other than being a
cool word, means that it reads source code and converts it to other source code. In this case, it parses JavaScript, makes some
changes to it, and outputs JavaScript. Except that... out-of-the-box, Babel doesn't actually make any changes!

Adding babel-preset-env
We need a little bit of configuration to tell Babel to do the ES2015 to ES5 transformation. In other words, to turn our new
JavaScript into old JavaScript.

And they mention it right on the installation page! At the bottom, they tell you that you probably need something called
babel-preset-env . In Babel language, a preset is a transformation. If we want Babel to make the ES2015 transformation, we
need to install a preset that does that. The env preset is one that does that. And there are other presets, like CoffeeScript,
ActionScript and one for ReactJS that we'll cover in the future!

Let's install the preset with yarn:

$ yarn add babel-preset-env --dev

Perfect! To tell Babel to use that preset, at the root of the project, create a .babelrc file. Babel will automatically read this
configuration file, as long as we execute Babel from this directory. Inside, add "presets": ["env"] :

4 lines .babelrc 
1 {
2 "presets": ["env"]
3 }

This comes straight from the docs. And... we're done!

Try the command again! Run that diff command now:

$ ./node_modules/.bin/babel web/assets/js/RepLogApp.js -o web/assets/dist/RepLogApp.js


$ diff -u web/assets/js/RepLogApp.js web/assets/dist/RepLogApp.js

Woh! Now there are big differences! In fact, it looks like almost every line changed. Let's go look at the new RepLogApp.js file in
dist/ - it's really interesting.

Cool! First, Babel adds a few utility functions at the top:

 337 lines web/assets/dist/RepLogApp.js 


1 'use strict';
2
3 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descripto
4
5 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function
6
7 (function (window, $, Routing, swal) {
 ... lines 8 - 335
336 })(window, jQuery, Routing, swal);

Below, instead of using the new class syntax, it calls one of those functions - _createClass() - which helps to mimic that new
functionality:

 337 lines web/assets/dist/RepLogApp.js 


 ... lines 1 - 6
7 (function (window, $, Routing, swal) {
 ... lines 8 - 10
11 var RepLogApp = function () {
11 var RepLogApp = function () {
12 function RepLogApp($wrapper) {
13 _classCallCheck(this, RepLogApp);
14
15 this.$wrapper = $wrapper;
16 this.repLogs = new Set();
17
18 HelperInstances.set(this, new Helper(this.repLogs));
19
20 this.loadRepLogs();
21
22 this.$wrapper.on('click', '.js-delete-rep-log', this.handleRepLogDelete.bind(this));
23 this.$wrapper.on('click', 'tbody tr', this.handleRowClick.bind(this));
24 this.$wrapper.on('submit', RepLogApp._selectors.newRepForm, this.handleNewFormSubmit.bind(this));
25 }
26
27 /**
28 * Call like this.selectors
29 */
30
31
32 _createClass(RepLogApp, [{
33 key: 'loadRepLogs',
34 value: function loadRepLogs() {
35 var _this = this;
36
37 $.ajax({
38 url: Routing.generate('rep_log_list')
39 }).then(function (data) {
40 var _iteratorNormalCompletion = true;
41 var _didIteratorError = false;
42 var _iteratorError = undefined;
43
44 try {
45 for (var _iterator = data.items[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorN
46 var repLog = _step.value;
47
48 _this._addRow(repLog);
49 }
50 } catch (err) {
51 _didIteratorError = true;
52 _iteratorError = err;
53 } finally {
54 try {
55 if (!_iteratorNormalCompletion && _iterator.return) {
56 _iterator.return();
57 }
58 } finally {
59 if (_didIteratorError) {
60 throw _iteratorError;
61 }
62 }
63 }
64 });
65 }
 ... lines 66 - 252
253 }], [{
254 key: '_selectors',
255 get: function get() {
256 return {
257 newRepForm: '.js-new-rep-log-form'
258 };
258 };
259 }
260 }]);
261
262 return RepLogApp;
263 }();
 ... lines 264 - 335
336 })(window, jQuery, Routing, swal);

Our arrow functions are also gone, replaced with classic anonymous functions.

There's a lot of cool, but complex stuff happening here. And fortunately, we don't need to worry about any of this! It just works!
Now, even an older browser can enjoy our awesome, new code.

 Tip

The purpose of the babel-preset-env is for you to configure exactly what versions of what browsers you need to support. It
then takes care of converting everything necessary for those browsers.

Babel and the Polyfill


But wait... it did not change our WeakMap !

 337 lines web/assets/dist/RepLogApp.js 


 ... lines 1 - 6
7 (function (window, $, Routing, swal) {
8
9 var HelperInstances = new WeakMap();
 ... lines 10 - 335
336 })(window, jQuery, Routing, swal);

But... isn't that only available in ES2015? Yep! Babel's job is to convert all the new language constructs and syntaxes to the old
version. But if there are new objects or functions, it leaves those. Instead, you should use something called a polyfill.
Specifically, babel-polyfill . This is another JavaScript library that adds missing functionality, like WeakMap , if it doesn't exist in
whatever browser is running our code.

We actually did something just like this in the first episode. Remember when we were playing with the Promise object? Guess
what? That object is only available in ES2015. To prevent browser issues, we used a polyfill.

To use this Polyfill correctly, we need to go a little bit further and learn about Webpack. That's the topic of our next tutorial...
where we're going to take a huge step forward with how we write JavaScript. With webpack, we'll be able to do cool stuff like
importing JavaScript files from inside of each other:

// actually imports code from helper.js!


import myHelperFunctions from './helper';

myHelperFunctions.now();

Heck, you can even import CSS from inside of JavaScript. It's bananas.

Ok guys! I hope you learned tons about ES2015/ES6/Harmony/Larry! You can already start using it by using Babel. Or, if your
users all have brand-new browsers, then lucky you!

All right guys, I'll seeya next time.

You might also like