ES6 MVC JavaScript Tutorial: Build A Simple CRUD App

   Raja Tamil • Nov 27 • • 1 comments

In this ES6 MVC JavaScript tutorial, you’re going to learn how to build a simple CRUD app with 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. And, 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. Inside that

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 will see the output message render HTML here on the browser console.

At this stage, You have successfully hooked 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. Delcare 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 stored it in $contactListUI.

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

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

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

Finally, set an 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 inital value to null.

And check to see how the callback is called either click event or just invoke it from somewhere using typeof. If it’s object then, it’s being called by 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 take a index value and get return 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 a add button and the second one is a add 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 mouse overing 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 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 contact button and attach click event to it with 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 and create an object by setting key from the attribue data-key of input element and the value from value of input element on each interation.

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

Then, invoke renderContactListModule() method to re-render the View after the new data 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. 

Sharing is caring!