ES6 MVC JavaScript Tutorial: Build A Simple CRUD App

Javascript

In this ES6 MVC JavaScript tutorial, you’re going to learn how to build a simple CRUD app using ES6 Class.

STEP 01: Setting Up The Project

Here is the Address Book Project setup. It has just three simple files: HTML, CSS and JS.

1.  Create a folder structure.

| AddressBook (folder) 
| -- index.html 
| -- app.js 
| -- style.css

2. Link style.css and app.js file to the index.html file.

<!DOCTYPE html>
<html>

<head>
    <title>Address Book - How to write testable javascript code</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
    <script type="text/javascript" src="app.js"></script>
</body>

</html>

3. Create contact list module inside index.html where all of the contacts would be.

<body>
    <h2>Address Book</h2>

    <!-- contact list module -->
    <section>
        <ul id="contact-list">loading...</ul>
    </section>
</body>

2. Create An MVC Structure

Model

Add some Model Data which is the M part of MVC inside app.js . This could be a separate class fetching data from API via AJAX call. For simplicity sake, I have made a simple array of objects called contactsData.

// ============== Model ========================= 
const contactsData = [{
    'fname': 'Anbu',
    'lname': 'Arasan',
    'phone': '190-309-6101',
    'email': 'anbu.arasan@email.com'
}, {
    'fname': 'Arivu',
    'lname': 'Mugilan',
    'phone': '490-701-7102',
    'email': 'arivu.mugilan@email.com'
}, {
    'fname': 'Bob',
    'lname': 'Johnson',
    'phone': '574-909-3948',
    'email': 'bob.johnson@email.com'
}, {
    'fname': 'Raja',
    'lname': 'Tamil',
    'phone': '090-909-0101',
    'email': 'raja.tamil@email.com'
}, {
    'fname': 'Sundar',
    'lname': 'Kannan',
    'phone': '090-909-0101',
    'email': 'sundar.kannan@email.com'
}]

View

1.  Create a class name AddressBookView inside app.js which is the V (view) part of MVC. Add an init() method in it.

// ============== View ========================= 
class AddressBookView {
    init() {
        console.log("render HTML here");
    }
}

2. Define addressBookView object by instantiating AddressBookView class.

const addressBookView = new AddressBookView();

Controller

1. Declare a class called AddressBookCtrl which would be the controller.

The rule of thumb is, the Model and View can never talk to each other directly and the controller is the only one that should communicate to both of them.

//================ Controller ================== 
class AddressBookCtrl {
    constructor(addressBookView) {
        this.addressBookView = addressBookView;
    }
    init() {
        this.addressBookView.init();
    }
}

2. Make an addressBookCtrl object by instantiating AddressBookCtrl class and pass the addressBookView object as an argument to it like so. (Dependency Injection)

const addressBookApp = new AddressBookCtrl(addressBookView);

3. Start the application by invoking init() method of addressBookApp.

addressBookApp.init();

When addressBookApp.init() method is invoked, addressBookView.init() method is to be fired and we will see the output message render HTML here on the browser console.

At this stage, you have successfully hooked the Controller and View. 🙂

3. Get All Contacts To The View

1. Get all the model data by declaring getContacts() method inside AddressBookCtrl class.

getContacts() {
    return contactsData;
}

2. Declare renderContactListModule().

renderContactListModule() {
    //get all contacts and assign to contacts 
    const contacts = addressBookApp.getContacts();

    // cache #contact-list DOM 
    const $contactListUI = document.getElementById('contact-list');

    // clear HTML from the DOM 
    $contactListUI.innerHTML = '';

    for (let i = 0, len = contacts.length; i < len; i++) {
        let $li = document.createElement('li');
        $li.setAttribute('class', 'contact-list-item');
        $li.setAttribute('data-index', i);
        $li.innerHTML = `${contacts[i]['fname']},${contacts[i]['lname']}`;
        $contactListUI.append($li);
    }
}

Inside renderContactListModule() method, get the model data by invoking getContacts() method on addressBookApp.

Then, get a DOM reference to the contact list element and store it in $contactListUI.

After that, clear HTML from the $contactListUI if there were any previously.

Loop through the model data and create li element inside it. and set two attributes to it, which are class and data-index.

The value of data-index attribute holds an incremental integer on every iteration.

Finally, set the actual first name and last name data to the list item using HTML property and append li to ul.

3. Invoke renderContactListModule().

Call renderContactListModule() inside addressBookView.init()method.

init() {
    this.renderContactListModule();
}

At this stage, you will be able to see all the contacts on the browser.

4. Get Selected Contact

1. Add the contact details module HTML code in index.html file.

<!-- contact item details module   -->
<section>
    <div id="contact-item-details"> loading... </div>
</section>

2. Go back to app.js and add an Event listener to <li> element inside renderContactListModule() method before appending <li> to <ul>.

$li.addEventListener("click", this.renderContactDetailsModule);

3. Define renderContactDetailsModule() callback function inside AddressBookView class.

renderContactDetailsModule(e) {
    let selectedIndex = null;
    if (typeof e === 'object') {
        e.stopPropagation();
        selectedIndex = this.getAttribute('data-index')
    } else {
        selectedIndex = e;
    }

}

To make this call back function more accessible, define selectedIndex and set its initial value to null.

Check to see how the callback is called, either click event or just invoke it from somewhere using typeof. If it’s an object then it’s being called by a click event.

In that case, get the value of the data-index attribute from the clicked li and assign it to selectedIndex.

If e parameter is an object, you will need to use e.stopPropagation() in order to avoid event bubbling.

stopPropagation() will prevent parent click event when child clicks event trigger. Later, we will be adding a couple of child elements like edit and delete buttons inside li. At that time, we do not want to parent click event trigger (li) when we click the child click event (edit button).

4. Add the getContact() method to our AddressBookCtrl class.

getContact(index) {
    return contactsData[index];
}

This function simply takes an index value and get returns the object from the contactsData, based on the index value.

5. Get selected item using getContact() inside renderContactDetailsModule().

const selectedItem = addressBookApp.getContact(selectedIndex);

6. Obtain the DOM reference for details view and set the selected items data to it inside renderContactDetailsModule() as well.

const $ContactItemUI = document.getElementById('contact-item-details');

At this stage, the renderContactDetailsModule() function should look like this:

renderContactDetailsModule(e) {
    let selectedIndex = null;
    if (typeof e === 'object') {
        e.stopPropagation();
        selectedIndex = this.getAttribute('data-index')
    } else {
        selectedIndex = e;
    }

    const selectedItem = addressBookApp.getContact(selectedIndex);
    const $ContactItemUI = document.getElementById('contact-item-details');
    $ContactItemUI.innerHTML = `${selectedItem['fname']} <br> ${selectedItem['lname']} <br> ${selectedItem['phone']} <br> ${selectedItem['email']}`;

}

7. Add the CSS rule for the details element inside style.css.

/* Contact Item Details Module */
#contact-item-details {
    float: left;
    width: 200px;
    background: #333;
    overflow: auto;
    color: white;
    padding: 10px;
    margin-left: 1px;
}

8. Highlight selected item by declaring hightlightCurrentListItem() inside AddressBookView class.

hightlightCurrentListItem(selectedIndex) {
    const $ContactListItems = document.getElementsByClassName('contact-list-item');
    for (let i = 0, len = $ContactListItems.length; i < len; i++) {
        $ContactListItems[i].classList.remove('active');
    }
    $ContactListItems[selectedIndex].classList.add("active")
}

Invoke it inside renderContactDetailsModule(e) function.

That should do it!

5. Add New Contact

1. Create <section> element with an id=”add-contact-module“ inside index.html file. This element is going to have ALL the HTML code that belongs to Add Contact Module.

<section id="add-contact-module">
</section>


Then, add two elements inside it. The first one is an add button and the second one is an add a contact form.

<section id="add-contact-module">
    <button id="open-add-contact-form-btn">+</button>
    <form>
        <h2>Add Contact</h2>
        first name:<br>
        <input type='text' data-key='fname' class='add-contact-input'><br>
        last name:<br>
        <input type='text' data-key='lname' class='add-contact-input'><br>
        phone:<br>
        <input type='text' data-key='phone' class='add-contact-input'><br>
        email:<br>
        <input type='text' data-key='email' class='add-contact-input'><br>
        <button type='button' id="add-contact-btn">add</button>
    </form>

</section>

2. Add the CSS code inside style.css which will open up the add contact form when mousing over the add contact button.

#add-contact-module {
    display: inline-block;
    margin-bottom: 1px;
    margin-left: 8px;
}

#add-contact-module #open-add-contact-form-btn {
    background: #54bb7d;
    font-size: 1.5em;
    color: white;
    padding-bottom: 5px;
}

#add-contact-module form {
    position: absolute;
    padding: 10px;
    width: 150px;
    background-color: #e1e1e1;
    border: 1px solid #999;
    display: none;
}

#add-contact-module form input {
    width: 97%;
    margin: 2px 0;
}

#add-contact-module form button {
    background: #54bb7d;
    font-size: 1em;
    padding: 0px 10px;
    color: white;
    margin-top: 10px;
}

#add-contact-module:hover form {
    display: block;
}

3. AddContact() method will take the new contact object from the View and push it to the contactsData model array.

// ============== Controller (API) =========================

class AddressBookCtrl {

    constructor(addressBookView) {
        this.addressBookView = addressBookView;
    }

    init() {
        this.addressBookView.init();
    }

    getContacts() {
        return contactsData;
    }

    getContact(index) {
        return contactsData[index];
    }

    addContact(contact) {
        contactsData.push(contact);
    }

}

4. Declare addContactModule() inside AddressBookView class

addContactModule() {
   const $addContact = document.getElementById('add-contact-btn');
   $addContact.addEventListener("click", this.addContactBtnClicked.bind(this));
}

Inside it, get a DOM reference to add the contact button and attach a click event to it with a callback function.

5. Create addContactBtnClicked() function.

addContactBtnClicked() {

    // get the add contact form inputs 
    const $addContactInputs = document.getElementsByClassName('add-contact-input');

    // this object will hold the new contact information
    let newContact = {};

    // loop through View to get the data for the model 
    for (let i = 0, len = $addContactInputs.length; i < len; i++) {

        let key = $addContactInputs[i].getAttribute('data-key');
        let value = $addContactInputs[i].value;
        newContact[key] = value;
    }

    // passing new object to the addContact method 
    addressBookApp.addContact(newContact);

    // render the contact list with the new data set
    this.renderContactListModule();

}

Inside it, get an array of input elements and loop through them. Create an object by setting the key from the attribute data-key of the input element and the value from the value of the input element on each iteration.

Then, invoke addContact() by passing the object as an argument which will add it to the contactsData model array.

Then, invoke renderContactListModule() method to re-render the View after the new data has been added.

6. Finally, call addContactModule() inside init() method on the AddressBookView class.

init() {
  this.renderContactListModule();
  this.renderContactDetailsModule(0);
  this.addContactModule();
}

At this stage, you should have an add contact functionality working like this.