DEV Community

Cover image for Build A Simple MVC CRUD Web App in JavaScript (ES6) Part 1
Raja Tamil
Raja Tamil

Posted on • Originally published at softauthor.com

Build A Simple MVC CRUD Web App in JavaScript (ES6) Part 1

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.

  • Create a folder structure.
| AddressBook (folder) 
| -- index.html 
| -- app.js 
| -- style.css
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode

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'
}]
Enter fullscreen mode Exit fullscreen mode

View

  • 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");
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Define addressBookView object by instantiating AddressBookView class.
const addressBookView = new AddressBookView();
Enter fullscreen mode Exit fullscreen mode

Controller

  • 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();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode
  • Start the application by invoking init() method of addressBookApp.
addressBookApp.init();
Enter fullscreen mode Exit fullscreen mode

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. 🙂

Get All Contacts To The View

  • Get all the model data by declaring getContacts() method inside AddressBookCtrl class.
getContacts() {
    return contactsData;
}
Enter fullscreen mode Exit fullscreen mode
  • 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

  • Invoke renderContactListModule().

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

init() {
    this.renderContactListModule();
}
Enter fullscreen mode Exit fullscreen mode

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

Get Selected Contact

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

<!-- contact item details module   -->
<section>
    <div id="contact-item-details"> loading... </div>
</section>
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode
  • 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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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).

  • Add the getContact() method to our AddressBookCtrl class.
getContact(index) {
    return contactsData[index];
}
Enter fullscreen mode Exit fullscreen mode

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

  • Get selected item using getContact() inside renderContactDetailsModule().
const selectedItem = addressBookApp.getContact(selectedIndex);
Enter fullscreen mode Exit fullscreen mode
  • 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');
Enter fullscreen mode Exit fullscreen mode

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']}`;

}
Enter fullscreen mode Exit fullscreen mode
  • 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;
}
Enter fullscreen mode Exit fullscreen mode
  • 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")
}
Enter fullscreen mode Exit fullscreen mode

Invoke it inside renderContactDetailsModule(e) function.

That should do it!

Add New Contact

  • Create 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
  • 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;
}
Enter fullscreen mode Exit fullscreen mode
  • 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);
    }

}
Enter fullscreen mode Exit fullscreen mode
  • Declare addContactModule() inside AddressBookView class
addContactModule() {
   const $addContact = document.getElementById('add-contact-btn');
   $addContact.addEventListener("click", this.addContactBtnClicked.bind(this));
}
Enter fullscreen mode Exit fullscreen mode

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

  • 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();

}
Enter fullscreen mode Exit fullscreen mode

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.

  • Finally, call addContactModule() inside init() method on the AddressBookView class.
init() {
  this.renderContactListModule();
  this.renderContactDetailsModule(0);
  this.addContactModule();
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)