JavaScript by Example

4.3 (6 reviews total)
By Dani Akash S
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies

About this book

JavaScript is the programming language that all web developers need to learn. The first item on our JavaScript to-do list is building g a To-do list app, which you'll have done by the end of the first chapter. You'll explore DOM manipulation with JavaScript and work with event listeners. You'll work with images and text to build a Meme creator. You will also learn about ES (ECMAScript) classes, and will be introduced to layouts using the CSS3 Flexbox.

You'll also develop a responsive Event Registration form that allows users to register for your upcoming event and use charts and graphics to display registration data. You will then build a weather application, which will show you different ways perform AJAX requests and work with dynamic, external data. WebRTC enables real-time communication in a web browser; you'll learn how to use it when you build a real-time video-call and chat application later in the book.

Towards the end of the book, you will meet React, Facebook's JavaScript library for building user interfaces. You'll throw together a blog with React, and get a feel for why this kind of JavaScript framework is used to build large-scale applications. To make your blog more maintainable and scalable, you'll use Redux to manage data across React components.

Publication date:
August 2017
Publisher
Packt
Pages
298
ISBN
9781788293969

 

Chapter 1. Building a ToDo List

 

Hi there!

We are going to be building some really interesting applications with JavaScript in this book. JavaScript has evolved from being a simple scripting language that is used for form validation in browsers to a powerful programming language that is used practically everywhere. Check out these use cases:

  • Want to set up a server to handle millions of requests with a lot of I/O operations? You have Node.js with its single threaded non-blocking I/O model that can handle the heavy load with ease. Write JavaScript on the server with Node.js frameworks, such as Express or Sails.
  • Want to build a large scale web application? This is an exciting time to be a frontend developer, since lots of new JavaScript frameworks, such as React, Angular 2, Vue.js, and so on, are available to speed up your development process and build large scale applications easily.
  • Want to build a mobile app? Pick up React Native or NativeScript and you can build truly native mobile applications that work across both iOS and Android with a single codebase written in JavaScript. Not enough? Use PhoneGap or Ionic to simply create a mobile application with HTML, CSS, and JavaScript. Just like a web app!
  • Want to build a desktop app? Use Electron to build a cross-platform native desktop application using HTML, CSS, and of course, JavaScript.
  • JavaScript is also playing an important role in building Virtual Reality (VR) and Augmented Reality (AR) applications. Check out React VR, A-Frame for building WebVR experiences and Argon.js, AR.js for adding AR to your web applications.

JavaScript is also evolving rapidly. With the introduction of ECMAScript 2015 (ES6), a lot of new additions came into the language that simplify a lot of work for developers, providing them with features that were previously only possible using TypeScript and CoffeeScript. Even more, features are being added to JavaScript in its new specifications (ES7 and beyond). This is an exciting time to be a JavaScript developer and this book aims at building a solid foundation so that you can adapt to any of the earlier mentioned JavaScript platforms/frameworks in the future.

This chapter is targeted at readers who know the basic concepts of HTML, CSS, and JavaScript, but are yet to learn new topics, such as ES6, Node, and so on. In this chapter, the following topics will be covered:

  • Document Object Model (DOM) manipulation and event listeners
  • Introduction to and the practical usage of the ES6 implementation of JavaScript
  • Using Node and npm for frontend development
  • Using Babel to transpile ES6 to ES5
  • Setting up an automated development server with npm scripts

If you feel you are comfortable with these topics, you can jump over to the next chapter, where we will be dealing with some advanced tools and concepts.

 

System requirements


JavaScript is the language of the web. So, you can build web applications from any system with a web browser and a text editor. But we do need some tools for building modern complex web applications. For better development experience, it's recommended to use a Linux or Windows machine with minimum 4 GB RAM or a Mac machine. Before we start, you might want to set up some of the following applications in your system.

Text editor

First of all, you need a JavaScript-friendly text editor. Text editors are important when it comes to writing code. Depending on the features they provide, you can save hours of development time. There are some really good text editors out there with excellent languages support. We are going to be using JavaScript in this book, so I'd recommend getting one of these open source JavaScript-friendly text editors:

You can also try Sublime Text: https://www.sublimetext.com/, which is a great text editor, but unlike the previously mentioned ones, Sublime Text is commercial and you need to pay for continued usage. There is also another commercial product WebStorm: https://www.jetbrains.com/webstorm/, which is a full-fledged Integrated Development Environment (IDE) for JavaScript. It comes with various tools for debugging and integration with JavaScript frameworks. You might want to give it a try sometime.

Note

I would recommend using Visual Studio Code (VSCode) for the projects in this book.

Node.js

Here's another important tool that we will be using throughout this book, Node.js. Node.js is a JavaScript runtime built on Chrome's V8 engine. It lets you run JavaScript outside your browser. Node.js has become really popular because it lets you run JavaScript on the server and is really fast thanks to its non-blocking I/O methods. One other excellent advantage of Node.js is that it helps create command-line tools, which can be used for various purposes, such as automation, code scaffolding, and more, many of which we will be using in this book. At the time of writing this book, the latest Long Term Support (LTS) version of Node.js is 6.10.2. I'll be using this version throughout this book. You can install the latest LTS version available at the time you are reading this book.

For Windows users

Installation on Windows is straightforward; just download and install the latest LTS version available at: https://nodejs.org/en/.

For Linux users

The easiest way is to install the latest LTS version through your package manager by following the instructions provided at https://nodejs.org/en/download/package-manager/.

For Mac users

Install Node.js using Homebrew:

  • Install Homebrew from: https://brew.sh/
  • Run the following command in terminal: brew install node

Once you have installed Node.js, run node -v in your Terminal (command prompt for Windows users) to check whether it is properly installed. This should print the current version of the node you have installed.

Google Chrome

Finally, install the latest version of Google Chrome: https://www.google.com/chrome/ in your system. You can use Firefox or other browsers, but I will be using Chrome, so it will be easier for you to follow if you use Chrome.

Now that we have all the necessary tools installed in our system, let's get started with building our first application!

 

ToDo List app


Let's take a look at the application we are about to build:

We are going to build this simple ToDo List app, which allows us to create a list of tasks, mark them as completed, and delete tasks from the list.

Let's get started by using the starter code of Chapter 1 in the book's code files. The starter code will contain three files: index.html, scripts.js, and styles.css. Open the index.html file in a web browser to see the basic design of the ToDo List app, as shown in the preceding screenshot.

The JavaScript file will be empty, in which we are going to write scripts to create the application. Let's take a look at the HTML file. In the <head> section, a reference to the styles.css file and BootstrapCDN are included, and at the end of the <body> tag, jQuery and Bootstrap's JS files are included along with our scripts.js file:

  • Bootstrap is a UI development framework that helps us to build responsive HTML designs faster. Bootstrap comes with set of JavaScript codes that requires jQuery to run.

  • jQuery is a JavaScript library that simplifies JavaScript functions for DOM traversal, DOM manipulation, event handling, and so on.

Note

Bootstrap and jQuery are widely used together for building web applications. In this book, we will be focusing more on using JavaScript. Hence, both of them will not be covered in detail. However, you can take a look at w3school's website for learning Bootstrap: https://www.w3schools.com/bootstrap/default.asp and jQuery: https://www.w3schools.com/jquery/default.asp in detail.

In our HTML file, the styles in the CSS file included last will overwrite the styles in the previous file. Hence, it's a good practice to include our own CSS files after the default framework's CSS files (Bootstrap in our case) if we plan to rewrite any of the framework's default CSS properties. We don't have to worry about CSS in this chapter, since we are not going to edit default styles of Bootstrap in this chapter. We only need to concentrate on our JS files. JavaScript files must be included in the given order as in the starter code:

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="scripts.js"></script>

We are including the jQuery code first after which Bootstrap JS files are included. This is because Bootstrap's JS files require jQuery to run. If we include Bootstrap JS first, it will print an error in the console, saying Bootstrap requires jQuery to run. Try moving the Bootstrap code above the jQuery code and open up your browser's console. For Google Chrome, it's Ctrl+Shift+J on Windows or Linux and command+option+J on Mac. You will receive an error similar to this:

Hence, we are currently managing dependencies by including the JS files in the right order. However, in larger projects, this could be really difficult. We'll look at a better way to manage our JS files in the next chapter. For now, let's continue on to build our application.

The body of our HTML file is divided into two sections:

  • Navigation bar
  • Container

We usually use the navigation bar to add links to the different sections of our web app. Since we are only dealing with a single page in this app, we will only include the page title in the navigation bar.

Note

I have included many classes to the HTML elements, such as navbar, navbar-inverse, navbar-fixed-top, container, col-md-2, col-xs-2, and so on. They are used for styling the elements using Bootstrap. We'll discuss them in later chapters. For now, let's focus only on the functionality part.

Chrome DevTools

In the body section, we have an input field with a button to add a new task and an unordered list to list out the tasks. The unordered list will have a checkbox to mark the task as completed and a delete icon to remove the task from the list. You might notice that the first item in the list is marked completed using a strike-through line. If you inspect the element using Chrome DevTools, you will notice that it has an additional class complete, which adds a strike-through line on the text using CSS, which is defined in our styles.css file.

To inspect an element using Chrome DevTools, right-click over that element and select inspect. You can also click Ctrl+Shift+C on Windows or Linux, or command+shift+C on Mac, and then, hover the cursor over the element to see its details. You can also directly edit the element's HTML or CSS to see the changes reflected on the page. Delete the complete class from the div of the first item in the list. You'll see that the strike-through line has gone. The changes made directly in the DevTools are temporary and will be cleaned when the page is refreshed. Take a look at the following image for a list of tools available to inspect an element in Chrome:

  • A: Inspect element from right-click
  • B: Click the cursor icon and select a different element by hovering the cursor over the element
  • C: Directly edit the HTML of the page
  • D: Directly edit the CSS associated with an element

One other nice feature of Chrome DevTools is that you can write debugger anywhere in your JavaScript code and Google Chrome will pause the execution of the script at the point in which debugger was called. Once the execution is paused, you can hover your cursor over the source code in sources tab and it will show the value contained in the variable in a popup. You can also type in the variable's name in the console tab to see its value.

This is the screenshot of Google Chrome debugger in action:

Feel free to explore the different sections of the Chrome Developer Tools to understand more about the tools it provides for the developers.

Getting started with ES6

Now that you have a good idea about the developer tools, let's start the coding part. You should already be familiar with the JavaScript ES5 syntax. So, let's explore JavaScript with the ES6 syntax in this chapter. ES6 (ECMAScript 2015) is the sixth major release of ECMAScript language specification. JavaScript is an implementation of ECMAScript language specification.

Note

At the time of writing this book, ES8 is the latest release of JavaScript language. However, for simplicity and ease of understanding, this book only focuses on ES6. You can always learn about the latest features introduced in ES7 and beyond on the Internet easily once you grasp the knowledge of ES6.

At the time of writing this book, all the modern browsers support most of the ES6 features. However, older browsers don't know about the new JavaScript syntax and, hence, they will throw errors. To resolve such backward compatibility issues, we will have to transpile our ES6 code to ES5 before deploying the app. Let's look into that at the end of the chapter. The latest version of Chrome supports ES6; so, for now, we'll directly create our ToDo List with the ES6 syntax.

I'll explain in detail about the new ES6 syntax. If you find difficulties understanding normal JavaScript syntax and data types, do refer to the respective section in the following w3schools page: https://www.w3schools.com/js/default.asp.

Open up the scripts.js file in your text editor. First of all, we will create a class that contains the methods of our ToDo List app, and yeah! Classes are a new addition to JavaScript in ES6. It's simple to create objects using classes in JavaScript. It lets us organize our code as modules. Create a class named ToDoClass with the following code in the scripts file and refresh the browser:

class ToDoClass {
  constructor() {
    alert('Hello World!');
  }
}
window.addEventListener("load", function() {
  var toDo = new ToDoClass();
});

Your browser will now throw an alert saying "Hello World!". So here's what the code is doing. First, window.addEventListener will attach an event listener to the window and wait for the window to finish loading all the needed resources. Once it is loaded, the load event is fired, which calls the callback function of our event listener that initializes ToDoClass and assigns it to a variable toDo. While ToDoClass is initialized, it automatically calls the constructor, which creates an alert saying "Hello World!". We can further modify our code to take advantage of ES6. In the window.addEventListener part, you can rewrite it as:

let toDo;
window.addEventListener("load", () => {
  toDo = new ToDoClass();
});

First, we replace the anonymous callback function function () {} with the new arrow function () => {}. Second, we define the variable with let instead of var.

Arrow functions

Arrow functions are a cleaner and shorter way to define functions in JavaScript and they simply inherit the this object of its parent instead of binding its own. We'll see more about the this binding soon. Let's just look into using the new syntax. Consider the following functions:

let a = function(x) {
}
let b = function(x, y) {
}

The equivalent arrow functions can be written as:

let a = x => {}
let b = (x,y) => {}

You can see that () are optional, when we have to pass the only single argument to the function.

Sometimes, we just return a value in a single line in our functions, such as:

let sum = function(x, y) {
  return x + y;
}

If we want to directly return a value in our arrow function in a single line, we can directly ignore the return keyword and {} curly braces and write it as:

let sum = (x, y) => x+y;

That's it! It will automatically return the sum of x and y. However, this can be used only when you want to return the value immediately in a single line.

let, var, and const

Next, we have the let keyword. ES6 has two new keywords for declaring variables, let and const. let and var differ by the scope of the variables declared using them. The scope of variables declared using var is within the function it is defined and global if it is not defined inside any function, while the scope of let is restricted to within the enclosing block it was declared in and global if it is not defined inside any enclosing block. Look at the following code:

var toDo;
window.addEventListener("load", () => {
  var toDo = new ToDoClass();
});

If you were to accidentally re-declare toDo somewhere along the code, as follows, your class object gets overwritten:

var toDo = "some value";

This behavior is confusing and quite difficult to maintain variables for large applications. Hence, let was introduced in ES6. It restricts the scope of variables only within the enclosing in which it was declared. In ES6, it is encouraged to use let instead of var for declaring variables. Look at the following code:

let toDo;
window.addEventListener("load", () => {
 toDo = new ToDoClass();
});

Now, even if you accidentally re-declare toDo somewhere else in the code, JavaScript will throw an error, saving you from a runtime exception. An enclosing block is a block of code between two curly braces {} and the curly braces may or may not belong to a function.

We need a toDo variable to be accessible throughout the application. So, we declare toDo above the event listener and assign it to the class object inside the callback function. This way, the toDo variable will be accessible throughout the page.

Note

let is very useful for defining variables in for loops. You can create a for loop such that for(let i=0; i<3; i++) {} and the scope of the variable i will only be within the for loop. You can easily use the same variable name in other places of your code.

Let's take a look at the other keyword const. The working of const is the same as that of let, except that variables declared using const cannot be changed (reassigned). Hence, const is used for constants. However, an entire constant cannot be reassigned but their properties can be changed. For example:

const a = 5;
a = 7; // this will not work
const b = {
  a: 1,
  b: 2
};
b = { a: 2, b: 2 }; // this will not work
b.a = 2; // this will work since only a property of b is changed

Note

While writing code in ES6, always use const to declare your variables. Use let only when you need to perform any changes (reassignments) to the variable and completely avoid using var.

The toDo object contains the class variables and functions as properties and methods of the object. If you need a clear picture of how the object is structured in JavaScript, see: https://www.w3schools.com/js/js_objects.asp.

Loading the tasks from data

The first thing we want to do in our application is to load the tasks dynamically from a set of data. Let's declare a class variable that contains the data for tasks along with methods needed to pre-populate the tasks. ES6 does not provide a direct way to declare class variables. We need to declare variables using the constructor. We also need a function to load tasks into the HTML elements. So, we'll create a loadTasks() method:

class ToDoClass {
  constructor() {
    this.tasks = [
        {task: 'Go to Dentist', isComplete: false},
         {task: 'Do Gardening', isComplete: true},
         {task: 'Renew Library Account', isComplete: false},
    ];
    this.loadTasks();
  }

  loadTasks() {
  }
}

The tasks variable is declared inside the constructor as this.tasks, which means the tasks variable belongs to this (ToDoClass). The variable is an array of objects that contain the task details and its completion status. The second task is set to be completed. Now, we need to generate an HTML code for the data. We'll reuse the code of the <li> element from the HTML to generate a task dynamically:

 <li class="list-group-item checkbox">
  <div class="row">
    <div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 checkbox">
     <label><input type="checkbox" value="" class="" checked></label>
    </div>
    <div class="col-md-10 col-xs-10 col-lg-10 col-sm-10 task-text complete">
      First item
    </div>
     <div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 delete-icon-area">
      <a class="" href="/"><i class="delete-icon glyphicon glyphicon-trash"></i></a>
     </div>
   </div>
 </li>

Note

In JavaScript, an instance of a class is called the class object or simply object. The class objects are structured similarly to JSON objects in key-value pairs. The functions associated with a class object are called its methods and the variables/values associated with a class object are called its properties.

Template literals

Traditionally, in JavaScript, we concatenate strings using the + operator. However, if we want to concatenate multi-line strings, then we have to use the escape code \ to escape new lines, such as:

let a = '<div> \
    <li>' + myVariable+ '</li> \
</div>'

This can be very confusing when we have to write a string that contains a large amount of HTML. In this case, we can use ES6 template strings. Template strings are strings surrounded by backticks ` ` instead of single quotation marks ' '. By using this, we can create multi-line strings in an easier way:

let a = `
<div>
   <li> ${myVariable} </li>
</div>
`

As you can see, we can create DOM elements in a similar way; we type them in HTML without worrying about spaces or multi-lines. Because whatever formatting, such as tabs or new lines, present inside the template strings is directly recorded in the variable. And we can declare variables inside the strings using ${}. So, in our case, we need to generate a list of items for each task. First, we will create a function to loop through the array and generate the HTML. In our loadTasks() method, write the following code:

loadTasks() {
  let tasksHtml = this.tasks.reduce((html, task, index) => html +=  
  this.generateTaskHtml(task, index), '');
  document.getElementById('taskList').innerHTML = tasksHtml;
 }

After that, create a generateTaskHtml() function inside ToDoClass, with the code:

generateTaskHtml(task, index) {
 return `
  <li class="list-group-item checkbox">
   <div class="row">
    <div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 checkbox">
     <label><input id="toggleTaskStatus" type="checkbox"  
     onchange="toDo.toggleTaskStatus(${index})" value="" class="" 
     ${task.isComplete?'checked':''}></label>
    </div>
    <div class="col-md-10 col-xs-10 col-lg-10 col-sm-10 task-text ${task.isComplete?'complete':''}">
     ${task.task}
   </div>
   <div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 delete-icon-area">
     <a class="" href="/" onClick="toDo.deleteTask(event, ${index})"><i 
     id="deleteTask" data-id="${index}" class="delete-icon glyphicon 
     glyphicon-trash"></i></a>
    </div>
   </div>
  </li>
`;
}

Now, refresh the page, and wow! Our application is loaded with tasks from our tasks variable. That should look like a lot of code at first, but let's look into it line by line.

Note

In case the changes aren't reflected when you refresh the page, it's because Chrome has cached the JavaScript files and is not retrieving the latest one. To make it retrieve the latest code, you will have to do a hard reload by pressing Ctrl+Shift+R on Windows or Linux and command+Shift+R on Mac.

In the loadTasks() function, we declare a variable tasksHtml with a value that is returned by the callback function of the array reduce() method of the tasks variable. Each array object in JavaScript has some methods associated with it. reduce is one such method of JS array that applies a function to each element of the array from left to right and applies the values to an accumulator so that the array gets reduced to a single value and then it returns that final value. The reduce method accepts two parameters; first is the callback function, which is applied to each element of the array, and the second one is the initial value of the accumulator. Let's look at our function in normal ES5 syntax:

let tasksHtml = this.tasks.reduce(function(html, task, index, tasks) { 
  return html += this.generateTaskHtml(task, index)
}.bind(this), '');
  • The first parameter is the callback function, whose four parameters are html, which is our accumulator, task, which is an element from the tasks array, index, which gives the current index of the array element in the iteration, and tasks, which contains the entire array on which the reduce method is applied on (we don't need the entire array inside the callback function for our use case, so the fourth parameter is ignored in our code).
  • The second parameter is optional, which contains the initial value of the accumulator. In our case, the initial HTML string is an empty string ''.
  • Also, note that we have to bind the callback function with this (which is our class) object so that the methods of ToDoClass and the variables are accessible within the callback function. This is because, otherwise, every function will define its own this object and the parent's this object will be inaccessible within that function.

What the callback function does is it takes the empty html string (accumulator) first and concatenates it with the value returned by the generateTaskHtml() method of ToDoClass, whose parameters are the first element of the array and its index. The returned value, of course, should be a string, otherwise, it will throw an error. Then, it repeats the operation for each element of the array with an updated value of the accumulator, which is finally returned at the end of the iteration. The final reduced value contains the entire HTML code for populating our tasks as a string.

By applying ES6 arrow functions, the entire operation can be achieved in a single line as:

let tasksHtml = this.tasks.reduce((html, task, index) => html += this.generateTaskHtml(task, index), '');

Isn't that simple! Since we are just returning the value in a single line, we can ignore both the {} curly braces and return keyword. Also, arrow functions do not define their own this object; they simply inherit the this object of their parents. So we can also ignore the .bind(this) method. Now, we have made our code cleaner and much simpler to understand using arrow functions.

Before we move on to the next line of the loadTasks() method, let's look at the working of the generateTaskHtml() method. This function takes two arguments--an array element task in the tasks data and its index and returns a string that contains the HTML code for populating our tasks. Note that we have included variables in the code for the checkbox:

<input id="toggleTaskStatus" type="checkbox" onchange="toDo.toggleTaskStatus(${index})" value="" class="" ${task.isComplete?'checked':''}>

It says that "on change of checkbox's status", call toggleTaskStatus() method of the toDo object with the index of the task that was changed. We haven't defined the toggleTaskStatus() method yet, so when you click the checkbox on the website now, it will throw an error in Chrome's console and nothing special happens in the browser window. Also, we have added a conditional operator ()?: to return a checked attribute for the input tag if the task status is complete. This is useful to render the list with a prechecked check box if the task is already complete.

Similarly, we have included ${task.isComplete?'complete':''} in the div that contains the task text so that an additional class gets added to the task if the task is complete, and CSS has been written in the styles.css file for that class to render a strike-through line over the text.

Finally, in the anchor tag, we have included onClick="toDo.deleteTask(event, ${index})" to call the deleteTask() method of the toDo object with parameters--the click event itself and the index of the task. We haven't defined the deleteTask() method yet, so clicking on the delete icon is going to take you to the root of your file system!

Note

onclick and onchange are some of HTML attributes that are used to call JavaScript functions when the specified event occurs on the parent element on which the attributes are defined. Since these attributes belong to HTML, they are case insensitive.

Now, let's look at the second line of the loadTasks() method:

document.getElementById('taskList').innerHTML = tasksHtml;

We just replaced the HTML code of the DOM element with the ID taskList with our newly generated string tasksHTML. Now, the ToDo List is populated. Time to define the two new methods of the toDo object, which we included in our generated HTML code.

Managing task status

Inside ToDoClass, include the two new methods:

 toggleTaskStatus(index) {
  this.tasks[index].isComplete = !this.tasks[index].isComplete;
   this.loadTasks();
 }
 deleteTask(event, taskIndex) {
   event.preventDefault();
   this.tasks.splice(taskIndex, 1);
   this.loadTasks();
 }

The first method, toggleTaskStatus(), is used to mark a task as completed or incomplete. It is called when a checkbox is clicked (onChange) with the index of the task, which was clicked as the parameter:

  • Using the task's index, we assign the task's isComplete status as the negation of its current status not using the (!) operator. Hence, the completion status of the tasks can be toggled in this function.
  • Once the tasks variable is updated with new data, this.loadTasks() is called to re-render all the tasks with the updated value.

The second method, deleteTask(), is used to delete a task from the list. Currently, clicking the delete icon will take you to the root of the file system. However, before navigating you to the root of the file system, a call to toDo.deleteTask() is made with the click event and task's index as the parameters:

  • The first parameter event contains the entire event object that contains various properties and methods about the click event that just happened (try console.log(event) inside the deleteTask() function to see all the details in Chrome's console).
  • To prevent any default action (opening a URL) from happening once, we click the delete icon (the <a> tag). Initially, we need to specify event.preventDefault().
  • Then, we need to remove the task element of the array that was deleted from the tasks variable. For that, we use the splice() method, which deletes a specified number of elements from an array from a specified index. In our case, from the index of the task, which needs to be deleted, delete only a single element. This removes the task to be deleted from the tasks variable.
  • this.loadTasks() is called to re-render all the tasks with the updated value.

Refresh the page (hard reload if needed) to see how our current application works with the new code. You can now mark a task as completed and can delete a task from the list.

Adding new tasks to the list

We now have the options to toggle a task status and to delete a task. But we need to add more tasks to the list. For that, we need to use the text box provided in the HTML file to allow users to type in new tasks. The first step will be adding the onclick attribute to the add task <button>:

<button class="btn btn-primary" onclick="toDo.addTaskClick()">Add</button>

Now, every button click will call the addTaskClick() method of the toDo object, which is not yet defined. So, let's define it inside our ToDoClass:

addTaskClick() {
  let target = document.getElementById('addTask');
  this.addTask(target.value);
  target.value = ""
}
addTask(task) {
  let newTask = {
   task,
   isComplete: false,
  };
  let parentDiv = document.getElementById('addTask').parentElement;
  if(task === '') {
   parentDiv.classList.add('has-error');
  } else {
   parentDiv.classList.remove('has-error');
   this.tasks.push(newTask);
   this.loadTasks();
  }
}

Reload Chrome and try adding a new task by clicking the Add button. If everything's fine, you should see a new task get appended to the list. Also, when you click the Add button without typing anything in the input field, then it will highlight the input field with a red border, indicating the user should input text in the input field.

Note

See how I have divided our add task operation across two functions? I did a similar thing for the loadTask() function. In programming, it is a best practice to organize all the tasks into smaller, more generic functions, which will allow you to reuse those functions in the future.

Let's see how the addTaskClick() method works:

  • addTaskClick() function doesn't have any request parameters. First, to read the new task's text, we get the <input> element with the ID addTask, which contains the text needed for the task. using document.getElementById('addTask'), and assign it to target variable. Now, the target variable contains all the properties and methods of the <input> element, which can be read and modified (try console.log(target) to see all the details contained in the variable).
  • The value property contains the required text. So, we pass target.value to the addTask() function, which handles adding a new task to the list.
  • Finally, we reset the input field to an empty state by setting target.value to an empty string ''.

That's the event handling part for the click event. Let's see how the task gets appended to the list in the addTask() method. The task variable contains the text for the new task:

  • Ideally, the first step in this function is to construct the JSON data that defines our task:
let newTask = {
  task: task,
  isComplete: false
}
  • Here's another ES6 feature object literal property value shorthand; instead of writing {task: task} in our JSON object, we can simply write {task}. The variable name will become the key and the value stored in the variable becomes the value. This will throw an error if the variable is undefined.
  • We also need to create another variable parentDiv to store the object of the parent <div> element of our target <input> element. It's useful because, when the task is an empty string, we can add the has-error class to the parent element parentDiv.classList.add('has-error'), which by Bootstrap's CSS, renders a red border to our <input> element. This is how we can indicate to the user that they need to enter a text before clicking the Add button.
  • However, if the input text is not empty, we should remove the has-error class from our parent element to ensure the red border is not shown to the user and then simply push our newTask variable to the tasks variable of our class. Also, we need to call loadTasks() again so that the new task gets rendered.

Adding tasks by hitting Enter button

Well, this is one way of adding tasks, but some users prefer adding tasks directly by hitting the Enter button. For that, let's use event listeners to detect the Enter key press in the <input> element. We can also use the onchange attribute of our <input> element, but let's give event listeners a try. The best way to add event listeners to a class is to call them in the constructor so that the event listeners are set up when the class is initialized.

So, in our class, create a new function addEventListeners() and call it in our constructor. We are going to add event listeners inside this function:

constructor() {
  ...
  this.addEventListeners();
}
 addEventListeners() {
  document.getElementById('addTask').addEventListener('keypress', event => {
     if(event.keyCode === 13) {
       this.addTask(event.target.value);
       event.target.value = '';
     }
   });
 }

And that's it! Reload Chrome, type in the text, and hit Enter. This should add tasks to our list just like how the add button works. Let's go through our new event listener:

  • For every keypress happening in the <input> element with the ID addTask, we run the callback function with the event object as the parameter.
  • This event object contains the keycode of the key that was pressed. For the Enter key, the keycode is 13. If the key code is equal to 13, we simply call the this.addTask() function with the task's text event.target.value as its parameter.
  • Now, the addTask() function handles adding the task to the list. We can simply reset <input> back to an empty string. This is a great advantage of organizing every operation into functions. We can simply reuse the functions wherever they're needed.

Persisting data in the browser

Now, functionality-wise, our ToDo List is ready. However, on refreshing the page, the data will be gone. Let's see how to persist data in the browser. Usually, web apps connect with APIs from the server-side to load data dynamically. Here, we are not looking into server-side implementation. So, we need to look for an alternate way to store data in the browser. There are three ways to store data in the browser. They are as follows:

  • cookie: A cookie is a small information that is stored on the client-side (browser) by the server with an expiry date. It is useful for reading information from the client, such as login tokens, user preferences, and so on. Cookies are primarily used on the server-side and the amount of data that can be stored in the cookie is limited to 4093 bytes. In JavaScript, cookies can be managed using the document.cookie object.
  • localStorage: HTML5's localStorage stores information with no expiry date and the data will persist even after closing and opening the web page. It provides a storage space of 5 MB per domain.
  • sessionStorage: sessionStorage is equivalent to that of localStorage, except that the data is only valid per session (the current tab that the user is working on). The data expires when the website is closed.

For our use case, localStorage is the best choice for persisting task data. localStorage stores data as key-value pairs, while the value needs to be a string. Let's look at the implementation part. Inside the constructor, instead of assigning the value to this.tasks directly, change it to the following:

constructor() {
  this.tasks = JSON.parse(localStorage.getItem('TASKS'));
   if(!this.tasks) {
    this.tasks = [
       {task: 'Go to Dentist', isComplete: false},
       {task: 'Do Gardening', isComplete: true},
       {task: 'Renew Library Account', isComplete: false},
    ];
  } 
... 
}

We are going to save our tasks in localStorage as a string with 'TASKS' as its key. So when the user opens the website for the first time, we need to check whether any data is present in localStorage with the key 'TASKS'. If no data is present, it will return null, which means this is the first time a user is visiting the website. We need to use JSON.parse() to convert the data retrieved from localStorage from a string to an object:

  • If no data is present in localStorage (user visiting the site for the first time), we shall prepopulate some data for them using the tasks variable. The best place to add the code to persist task data in our application will be the loadTasks() function because it is called every time a change in tasks is made. In the loadTasks() function, add an additional line:
 localStorage.setItem('TASKS', JSON.stringify(this.tasks));
  • This will convert our tasks variable to string and store it in localStorage. Now, you can add tasks and refresh the page, and the data will be persisted in your browser.
  • If you want to empty localStorage for development purposes, you can use localStorage.removeItem('TASKS') to delete the key or you can use localStorage.clear() to completely remove all the data stored in localStorage.

Note

Everything in JavaScript has an inherent Boolean value, which can be called truthy or falsy. The following values are always falsy - null, "" (empty string), false, 0 (zero), NaN (not a number), and undefined. Other values are considered truthy. Hence, they can be directly used in conditional statements like how we used if(!this.tasks) {} in our code.

Now that our application is complete, you can remove the contents of the <ul> element in the index.html file. The contents will now be directly populated from our JavaScript code. Otherwise, you will see the default HTML code flash in the page when the page is loaded or refreshed. This is because our JavaScript code executes only after all the resources are finished loading due to the following code:

window.addEventListener("load", function() {
  toDo = new ToDoClass();
});

If everything works fine, then congratulations! You have successfully built your first JavaScript application and you have learned about the new ES6 features of JavaScript. Oh wait! Looks like we forgot something important!

Note

All the storage options discussed here are unencrypted and, hence, should not be used for storing sensitive information, such as password, API keys, authentication tokens, and so on.

Compatibility with older browsers

While ES6 works with almost all modern browsers, there are still many users who use older versions of Internet Explorer or Firefox. So, how are we going to make our application work for them? Well, the good thing about ES6 is that all it's new features can be implemented using the ES5 specification. This means that we can easily transpile our code to ES5, which will work on all modern browsers. For this purpose, we are going to use Babel: https://babeljs.io/, as the compiler for converting ES6 to ES5.

Remember how, in the beginning of our chapter, we installed Node.js in our system? Well, it's finally time to use it. Before we start compiling our code to ES5, we need to learn about Node and the npm.

Node.js and npm

Node.js is a JavaScript runtime built on Chrome's V8 engine. It lets developers run JavaScript outside of the browser. Due to the non-blocking I/O model of Node.js, it is widely used for building data-intensive, real-time applications. You can use it to build backend for your web application in JavaScript, just like PHP, Ruby, or other server-side languages.

One great advantage of Node.js is that it lets you organize your code into modules. A module is a set of code used to perform a specific function. So far, we have included the JavaScript code one after another inside the <script> tag in the browser. But in Node.js, we can simply call the dependency inside the code by creating the reference to the module. For example, if we need to jQuery, we can simply write the following:

const $ = require('jquery');

Or, we can write the following:

import $ from 'jquery';

The jQuery module will be included in our code. All the properties and methods of jQuery will be accessible inside the $ object. The scope of $ will be only within the file it is called. So, in each file, we can specify the dependencies individually and all of them will be bundled together during compilation.

But wait! For including jquery, we need to download the jquery package that contains the required module and save it in a folder. Then, we need to assign $ the reference of the file in the folder containing the module. And as the project grows, we will be adding a lot of packages and refer ring the modules in our code. So, how are we going to manage all the packages. Well, we have a nice little tool that gets installed along with Node.js called the Node Package Manager (npm):

  • For Linux and Mac users, npm is similar to one of these: apt-get, yum, dnf, and Homebrew.
  • For Windows users, you might not be familiar with the concept of package management yet. So, let's say you need jQuery. But you don't know what dependencies are needed for jQuery to run. That's where package managers come into play. You can simply run a command to install a package (npm install jquery). The package manager will read all the dependencies of the target package and install the target along with its dependencies. It also manages a file to keep track of installed packages. This is used for easily uninstalling the package in the future.

Note

Even though Node.js allows require/import of modules directly into the code, browsers do not support require or import functionality to directly import a module. But there are many tools available that can easily mimic this functionality so that we can use import/require inside our browsers. We'll use them for our project in the next chapter.

npm maintains a package.json file to store information regarding a package, such as its name, scripts, dependencies, dev dependencies, repository, author, license, and so on. A package is a folder containing one or more folder or files with a package.json file in its root folder. There are thousands of open source packages available in npm. Visit https://www.npmjs.com/ to explore the available packages. The packages can be modules that are used on the server-side or browser-side and command-line tools that are useful for performing various operations.

npm packages can be installed locally (per project) or globally (entire system). We can specify how we want to install it using different flags, as follows:

  • If we want to install a package globally, we should use the --global or -g flag.
  • If the package should be installed locally for a specific project, use the --save or -S flag.
  • If the package should be installed locally and it is only used for development purposes, use the --save-dev or -D flag.
  • If you run npm install <package-name> without any flags, it will install the package locally but will not update the package.json file. It is not recommended to install packages without the -S or -D flags.

Let's install a command-line tool using npm called http-server: https://www.npmjs.com/package/http-server. It is a simple tool that can be used to serve static files over an http-server just like how files are served in Apache or Nginx. This is useful for testing and developing our web applications, since we can see how our application behaves when it's served through a web server.

Command-line tools are mostly recommended to install globally if they are going to be used only by ourselves and not by any other developer. In our case, we are only going to be using the http-server package. So, let's install it globally. Open your Terminal/command prompt and run the following command:

npm install -g http-server

Note

If you are using Linux, some times you might face errors such as permission denied or unable to access file, and so on. Try running the same command as administrator (prefixed with sudo) for installing the package globally.

Once the installation is complete, navigate to the root folder of our ToDo List app in your terminal and run the following command:

http-server

You will receive two URLs and the server will start running, as follows:

  • To view the ToDo List app on your local device, open the URL starting with 127 in your browser
  • To view the ToDo List app on a different device connected to your local network, open the URL starting with 192 on the device's browser

Every time you open the application, http-server will print the served files in the terminal. There are various options available with http-server, such as -p flag, which can be used to change the default port number 8080 (try http-server -p 8085). Visit the http-server: https://www.npmjs.com/package/http-server, npm page for documentation on all available options. Now that we have a general idea of the npm packages, let's install Babel to transpile our ES6 code to ES5.

Note

We will be using Terminals a lot in our upcoming chapters. If you are using VSCode, it has an inbuilt terminal, which can be opened by pressing Ctrl+` on Mac, Linux, and Windows. It also supports opening multiple terminal sessions at the same time. This can save you lot time on switching between windows.

 

Setting up our development environment with Node and Babel


Babel is a JavaScript compiler, which is used to transpile JavaScript code from ES6+ to normal ES5 specification. Let's set up Babel in our project so that it automatically compiles our code.

There will be two different JS files in our project after setting up Babel. One is ES6, which we use to develop our app, and another will be the compiled ES5 code, which is going to be used by the browser. So, we need to create two different folders in our project root directory, namely, src and dist. Move the scripts.js file into the src directory. We are going to use Babel to compile the scripts from the src directory and store the result in the dist directory. So, in index.html, change the reference of scripts.js into <script src="dist/scripts.js"></script> so that the browser will always read the compiled code:

  1. To use npm, we need to create package.json in our project's root directory. Navigate to the project root directory in your terminal and type:

npm init
  1. First, it will ask your project's name, type in a name. For other questions, either type in some values or just hit Enter to accept default values. These values will be populated in the package.json file, which can be changed later.

  2. Let's install our development dependencies by running the following command in the terminal:

npm install -D http-server babel-cli babel-preset-es2015 concurrently
  1. This command will create a node_modules folder and install the packages inside it. Now, your package.json file will have the preceding packages in its devDependencies parameter and your current folder structure should be:
.
├── dist
├── index.html
├── node_modules
├── package.json
├── src
└── styles.css

Note

If you are using git or any other version control system in your project, add node_modules and the dist folder to .gitignore or a similar file. These folders need not be committed to version control and must be generated when needed.

Time to write scripts to compile our code. Inside the package.json file, there will be a parameter called scripts. By default, it will be the following:

 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },

test is one of the default commands for npm. When you run npm test in the terminal, it will automatically execute the script inside the test key's value in the terminal. As the name suggests, test is used for executing automated test cases. Some other default commands are start, stop, restart, shrinkwrap, and so on. These commands are very useful to run scripts when developing server-side applications with Node.js.

However, during frontend development, we may need more commands like the default commands. npm also allows us to create our commands to execute arbitrary scripts. However, unlike default commands (such as npm start), we cannot execute our own commands by running npm <command-name>; we have to execute npm run <command-name> in the terminal.

We are going to set up npm scripts so that running npm run build will generate a working build for our application with the compiled ES5 code and running npm run watch will spin up a development server, which we are going to use for development.

Change the contents of scripts section into the following:

"scripts": {
  "watch": "babel src -d dist --presets=es2015 -ws",
  "build": "rm -rf dist && babel src -d dist --presets=es2015",
  "serve": "http-server"
},

Well, that looks like a lot of scripts! Let's go through them one by one.

First, let's check out the watch script:

  • The function of this script is to start babel in the watch mode so that every time we make any change in our ES6 code inside src directory, it will automatically be transpiled into ES5 code inside the dist directory along with source maps, which is useful for debugging the compiled code. The watch mode will keep on continuing the process in the terminal until the execution is terminated (hitting Ctrl+C).

  • Execute npm run watch in the terminal from your project's root directory. You can see that Babel has started compiling the code and a new scripts.js file will be created inside the dist folder.

  • The scripts.js file will contain our code in the ES5 format. Open up index.html in Chrome and you should see our application running normally.

Here's how it works. Try running babel src -d dist --presets=es2015 -ws directly in the terminal. It will throw an error saying babel is not installed (the error message may vary depending on your operating system). This is because we haven't installed Babel globally. We only installed it within our project. So, when we run npm run watch, npm will look for the binaries for Babel inside the project's node_modules folder and execute the command using those binaries.

Delete the dist directory, and create a new script inside package.json--"babel": "babel src -d dist". We are going to use this script for learning how Babel works:

  • This script tells Babel compile all the JS files inside thesrcdirectory and save the resulting files insidedistdirectory. The dist directory will be created if it is not present. Here, the -d flag is used to tell Babel that it needs to compile files inside the entire directory.
  • Run npm run babel in the terminal and open up our new scripts.js file inside the dist directory. Well, the file is compiled, but unfortunately, the result is also in ES6 syntax, so the new scripts.js file is an exact copy of our original file!
  • Our goal is to compile our code to ES5. For that, we need to instruct Babel to use some presets during compilation. Look at our npm install command, we have installed a package called babel-preset-es2015 for this purpose.
  • In our Babel script, add the option --presets=es2015 and execute npm run babel again. This time the code will be compiled to ES5 syntax.
  • Open up our application in the browser, add debugger inside our constructor, and reload. We have a new problem; the sources will now contain the code in ES5 syntax, which makes it harder to debug our original code.
  • For this, we need to enable source maps using the -s flag that creates a .map file, which is used to map the compiled code back to the original source. Also, use the -w flag to put Babel in the watch mode.

And now our script will be the same as the one used in the watch command. Reload the application with the debugger and you can see that the sources will contain our original code even though it is using the compiled source.

Wouldn't it be nice if running a single command would also start up our development server using http-server. We cannot use && to concatenate two commands that run simultaneously. Since && will execute the second command, only after the first one completes.

We have installed another package called concurrently for this purpose. It is used for executing multiple commands together. Syntax for using concurrently is as follows:

concurrently "command1" "command2" 

When we execute npm run watch, we need to run both the current watch script and the serve script. Change the watch script into the following:

"watch": "concurrently \"npm run serve\" \"babel src -d dist --presets=es2015 -ws\"",

Try running npm run watch again. Now, you have a fully functional development environment, which will automatically serve the files as well as compile the code as you make changes to your JS code.

Shipping the code

Once development is done, for shipping the code if you use version control, add the node_modules and dist folder to the ignore list. Otherwise, send your code without the node_modules or dist folder. Other developers can simply run npm install to install dependencies and read the scripts inside the package.json file to build the project when needed.

Our npm run build command will remove the dist folder present inside the project folder and create a new dist folder with the latest build of JS code.

 

Summary


Congratulations! You have built your first JavaScript application with the new ES6 syntax. You have learned the following concepts in this chapter:

  • DOM manipulation and event listeners in JavaScript
  • ECMAScript 2015 (ES6) syntax of JavaScript
  • Chrome Developer Tools
  • The workings of Node and npm
  • Using Babel to transpile the ES6 code to ES5 code

In our current npm setup, we have simply created a compile script to transform our code into ES5. There are lots of other tools available to automate more tasks, such as minification, linting, image compression, and so on. We will use one such tool called Webpack in our next chapter.

About the Author

  • Dani Akash S

    Dani Akash S is a passionate, self-taught application developer who loves working on the JavaScript stack. He has worked on many JavaScript frameworks, such as React.js, React Native, Angular, Vue, Express, and Sails. He has built many web and mobile applications. In his free time, he loves to explore new technologies and contribute to open source projects on GitHub.

    You can find him on his Twitter handle: @DaniAkashS

    Browse publications by this author

Latest Reviews

(6 reviews total)
Very quick purchasing experience and great content.
mordern javascript and good chapters
I've not had time yet to review this book. It seems that it will be OK.

Recommended For You

Book Title
Unlock this book and the full library for FREE
Start free trial