Lesson 5 - Creating an OOP Diary In JavaScript
In the previous lesson, Reference And Value Data Types In JavaScript, we explained the differences between value and reference data types. We already know that when we store a class instance to a variable, it actually contains a reference to that instance. So we can use a single instance from several variables or simply pass it somewhere without being copied.
As promised, in today's object-oriented programming tutorial for JavaScript, we're going to start programming an electronic diary. We'll also use the knowledge from the previous lesson.
Preparation
But first let's think about what we're going to need. We'll create a simple
page, index.html
, with a form to add an entry at the top and an
entry list below. We'll insert two inputs into some container,
e.g. into the <div>
element. They will be of the
text
and date
types for the task name and its date.
Finally, we'll add a confirmation button. Below, we'll add a second
<div>
for the task list.
As for JavaScript, we can create the js/
folder and three
scripts: Diary.js
, Entry.js
, and app.js
.
We'll also link them in our page.
Therefore, the index.html
file could look like this:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Diary</title> </head> <body> <h1>Diary</h1> <div> <input type="text" id="name" placeholder="Fill in the task name"><br> <input type="date" id="date" placeholder="Fill in the date"><br> <button id="confirm">Save task</button> </div> <div id="task-list"> </div> <script src="js/Diary.js"></script> <script src="js/Entry.js"></script> <script src="js/app.js"></script> </body> </html>
The page might look like this:
We won't deal too much with the design for now.
Entry
Let's start with the Entry.js
file. As you might guess, the file
will contain a class representing one entry in our diary. Therefore, we can give
it different properties that we can change when creating or editing the entry.
So far it can be a name, a date, and whether the task has been completed. We'll
set the first 2 properties by the constructor and as far as the completion of
the task is concerned, we'll assume that it's always undone by default:
class Entry { constructor(name, date) { this.name = name; this.date = date; this.done = false; } }
Diary
Now let's move to the diary itself.
Constructor
Let's create a Diary
class with a constructor in the
Diary.js
file and declare several properties in it:
entries
- We'll create the diary entries as an empty array.language
- The language to format the date for may be useful in the future, as different languages have different formats. E.g. English dates look different than European. To set the language, we'll add a parameter to the constructor. Since we'll mostly want the English environment, we'll define it with the default value ofen-US
, which will be used if we don't specify the parameter.
The class might look like this:
class Diary { constructor(language = "en-US") { this.entries = []; this.language = language; } }
Selecting Page Elements
In the class, we're going to need to work with DOM elements on the page. You may have already encountered the principle of separating the application logic from the user interface. This basic programming rule is used e.g. by the MVC architecture.
In JavaScript frameworks that you'll get to after finishing this course, the application architecture is designed so that the code selecting elements on the page does not mix with another application code. It would be very confusing otherwise.
We won't create any complicated architecture in this basic OOP course, but we'll try to place all selections of elements on the page at one place in the class. This place will be the constructor. We'll create a few more properties there to store elements from the page that we're going to need further in the class:
nameInput
- The input element for the name of the entry being addeddateInput
- The input element for the date of the entry being addedconfirmButton
- The save buttonprintElement
- The element for listing the entries stored in the diary
The constructor will now look like this:
constructor(language = "en-US") { this.entries = []; this.language = language; this.nameInput = document.getElementById("name"); this.dateInput = document.getElementById("date"); this.confirmButton = document.getElementById("confirm"); this.printElement = document.getElementById("task-list"); }
We'll not select elements anywhere else in the class, because it'd be very unreadable.
Methods
Let's move to the diary's methods.
setEvents()
In order for our constructor not to be too long, we'll set the event handlers of the page elements in a separate method. In our case, it's just a click on the button.
So far, let's add a naive implementation of the button-handling method that won't work. We'll explain why in a moment:
setEvents() { this.confirmButton.onclick = function() { // this code won't work const entry = new Entry(this.nameInput.value, this.dateInput.value); this.entries.push(entry); this.printEntries(); }; }
The method wires a handler function to the onclick
event of the
this.confirmButton
button. That's still all fine. Inside, the
values from the inputs are taken and a new entry is created based on them. We
insert this new instance into the array. All the entries are then listed.
So what's wrong? If you paid attention in the JavaScript Basic Constructs, you know that:
Using function
to handle
element events changes the context and the this
keyword then references the element that caused the event. Therefore,
this
does not contain the instance of our class
anymore. This behavior is a flaw in JavaScript's design and prevents us
from working with instance variables and methods from within event handlers.
Arrow functions
There are several workarounds. We'll mention the simplest solution. We'll use
an arrow function to handle the event, which is a "shorter"
syntax to declare functions. The name is based on the arrow sign we use to
declare these. Arrow functions were added to JavaScript later, so they
no longer suffer from the context change error. More precisely, they
have no context, and the this
keyword contains
what was there before, unchanged.
To store an arrow function to a variable, we use the following syntax:
functionName = () => { // function body }
If we wanted to pass parameters to the function, we write them in parentheses as we are used to:
functionName = (parameter) => { // function body }
If we want to pass one parameter only, we can even omit the parentheses:
functionName = parameter => { // function body }
Now, we'll fix the setEvents()
method to make the
this
keyword work in the handler function. We need the
this
keyword to access our class' properties:
setEvents() { this.confirmButton.onclick = () => { // this now stays this const entry = new Entry(this.nameInput.value, this.dateInput.value); this.entries.push(entry); this.printEntries(); }; }
We'll call the method at the end of the constructor:
this.setEvents();
printEntries()
Nothing will surprise us in the method for listing tasks, it works very much like the list of employees we created in previous lessons:
printEntries() { this.printElement.innerHTML = ""; for (let i = 0; i < this.entries.length; i++) { const entry = this.entries[i]; this.printElement.innerHTML += `<h3>${entry.name}</h3>when: ${entry.date}<br>done: ${entry.done}`; } }
The method deletes all the content from our element and lists our tasks one by one using a loop.
App.js
Finally, we still need to create a Diary
instance in
app.js
and list the saved entries. They aren't there yet at the
moment of creating the diary, but we'll retrieve them from the local storage
further in the course:
const diary = new Diary(); diary.printEntries();
If we now run our app in the browser, it'll look like this:
In case of any problems, compare your code with the working solution in the attachment.
In the next lesson, Data storage in JavaScript, we'll think about storing entries so they won't get lost after refreshing or closing the page. We'll talk about the popular JSON format. And we'll also group the tasks on the same day and make the listing of the entries nicer.
In the next lesson, Data storage in JavaScript, we will describe different types of data storage in JavaScript and the differences between them.