Writing clean code in Javascript using Object Literal

5 min read. Jul 3, 2021

Background

Code in Javascript (or any languages) can get really messy when you do not have standard conventions for namespace, naming variables and so on. If you structure your JavaScript code the old school way, i.e, splitting the script into multiple files and defining the methods and variables in global scope then you probably have ran into issues as below:

  • Name collisions due to the lack of namespace
  • Difficult to organize code as project grows
  • Poorer code readability and tough time to encounter bugs

To be honest, I have struggled in the past when I did not have proper namespace in the code. There are various design patterns such as module pattern, revealing module pattern, object literals and so on that solves the issue.

In this first part of writing clean code in JavaScript, I will walk you through the basic concept and usage of object literals on organizing code and creating namespace in JavaScript.

 

Object Literal Notation

In JavaScript, the simplest way to create an object is by using a set of curly braces {}, which is known as object literal notation. The notation uses comma-separated key-value pairs.

var app = {
  name: "Batman",
  alter_ego: "Bruce Wayne"
}

Apart from assigning primitive values, you can also assign function to them as below:

var app = {
  name: "Batman",
  alter_ego: "Bruce Wayne"

  greeting: function(){
    console.log("I'm "+ this.name)
  }

}

this operator is used to access the local variables and methods within the object literal.

You can call the method in object literals from outside as below:

app.greeting(); //This will print: I'm Batman

Likewise, new member can be assigned outside of an object as below:

app.comics = "DC";

The beauty of object literal is that we can go to any number of deep-nesting.

app.comics.city = "Gotham";

But do keep in mind that we need to define the higher-level parent before deeper-nested children.

The below assignment will throw a TypeError since we're trying to assign a new member to app.vehicle without defining it first.

app.vehicle.name = "Batmobile";

We can now extend the above concept to create namespace and encapsulate the code on our project.

 

Code organization using Object literals

I will demonstrate the implementation of object literals in organizing code by building a simple todo app that lets users to:

  • add task to the incomplete list
  • move them to completed list
  • and finally delete from completed list

It is a good practice to split the code into smaller files based on their specific purpose. As the todo app has three main features, we can thus split the script into three files and give them meaningful names.

The structure of the project is depicted as below:

  • - todo_app/
  •     - src/
  •       - css/
  •       - js/
  •         - todo/
  •             - app.todo.js
  •             - app.todo.add.js
  •             - app.todo.completed.js
  •             - app.todo.incomplete.js
  •         - app.js
  •       - index.html

app.js - This file is the entry point of the project. We will define our root object literal in this file which will be expanded by other files within the project.

app.todo.js - This file initializes other modules within todo namespace.

app.todo.add.js - This file deals with code that adds tasks to the incomplete list.

app.todo.incomplete.js - This file is responsible for moving tasks from incomplete to completed list.

app.todo.complete.js - Lastly, we'll write the code to remove tasks from the completed list on this file.

Let's take a look on our app.js

var app = {
  init: function(){
    app.todo.init();
    //Initialize sub-modules here
  },
}

The init function inside the app is the entry point of the application (kind of like the main function in Java). Initialization of the methods on other modules can be done here. This is a good place to bind event listeners within the project.

Now, let's have a look on our app.todo.js

app.todo = {
  init: function(){
    app.todo.add.init();
  },
}

You might have noticed that I've used init method again in the above code. Since it is in a different namespace there is no chance of name collision. The above code simply initializes the method on app.todo.add.js.

Now, let's have a look on our app.todo.add.js

app.todo.add = {
  task_name: document.getElementById('task_name'),
  btn_add_task: document.getElementById('btn_add_task'),

  init: function(){
    ...
  },
}

As you can see, we are deep nesting the object literal created in app.todo.js to namespace the code in this file.

Now, let's write an event handler and methods that add tasks to the incomplete list.

app.todo.add = {
  task_name: document.getElementById('task_name'),
  btn_add_task: document.getElementById('btn_add_task'),

  init: function(){
    this.bind_events();
  },
 
  bind_events: function(){
    this.btn_add_task.addEventListener('click',     this.add_task.bind(this));
  },

  add_task: function () {
    var task = this.create_task(this.task_name.value);
    app.todo.incomplete.wrapper.appendChild(task);
    app.todo.incomplete.bind_events(task);
    this.task_name.value = "";
  },

  create_task: function (task_name) {
    var task = document.createElement('div'),
        label = document.createElement("label"),
        button = document.createElement("button");

    label.innerHTML = task_name;
    button.innerHTML = 'Complete';
    button.classList.add('complete');

    task.appendChild(label);
    task.appendChild(button);

    return task;
  },
}

As you have noticed, the event handler for the add task button is registered and bound to add_task method. It is important to pass this parameter while binding since it allows us to reference the member variables and methods of the current object from the bound method (i.e. add_task). If we did not pass this parameter while binding, then this would refer to the add_task button events.

The first statement on add_task method invokes create_task method which creates a DOM element of an individual task by taking a user entered task as an argument.

The second statement app.todo.incomplete.wrapper refers to the variable in the external file (app.todo.incomplete.js) where the reference to the DOM element of incomplete todo list is stored. When a user clicks add task button after entering the task name, it will be appended to the incomplete list by using the in-built method appendChild.

Similarly, the third statement is used to bind complete button of a recently created task.

Finally, the last statement clears the input box so that the user can add a new task.

Now, let's have a look on app.todo.incomplete.js:

app.todo.incomplete = {
  wrapper: document.querySelector('.incomplete-list'),

  bind_events: function(task){
    var btn_complete = task.querySelector('.complete');
    btn_complete.onclick = this.move_completed_task;
  },

  move_completed_task: function(){
    var current_task = this.parentNode;

    var btn_complete = current_task.querySelector('.complete');
    btn_complete.remove();

    var btn_delete = document.createElement('button');
    btn_delete.innerText = 'Delete';
    btn_delete.classList.add('delete');

    current_task.appendChild(btn_delete);
    app.todo.completed.wrapper.appendChild(current_task);
    app.todo.completed.bind_events(current_task);
  }
}

In the code above, bind_events method is used to bind the click event of complete button which allows users to move tasks to the completed list. If you remember then it was referenced from app.todo.add.js a while ago.

Similarly, we have removed the complete button, hooked delete button on the task and bind new event listener on the code that follows it.

Let's peek at the last file, app.todo.complete.js

app.todo.completed = {
  wrapper: document.querySelector('.completed-list'),

  bind_events: function(task){
    var btn_delete = item.querySelector('.delete');
    btn_delete.onclick = this.delete_completed_task;
  },

  delete_completed_task: function(){
    var current_item = this.parentNode;
    current_item.remove();
  }
}

In the above code, the click event of delete task button on the completed list is bound to delete_completed_task method inside bind_events method. The button will delete the task from DOM when the user clicks on it.

Finally, the init method of todo app needs to be called from HTML source file as below:

app.init();

 

Wrapping up

Object literal is undoubtedly a great tool to create namespaces in JavaScript. Apart from that, it also helps to organize code and create encapsulation in the project, which I hope I've clearly demonstrated in the above todo app.

You can find the source code of the above todo app in my GitHub repository.

If you have any questions, feel free to get in touch with me.