Your First JavaScript App – V
Read the first part of this series.
The first thing we will do now, is to create the view of our application. We will first create the form, then we will add some dummy data so that we can create our list as well.
I have created a utility function here to create an HTML element and assign attributes to it
// scripts/modules/contacts/contactsViewTemplates.js
var ContactsViewTemplates = (function () {
function Cvt () {};
/**
* @description function creates an element with given tagName and
* options.
*/
Cvt.prototype.getElement = function (tagName, options, text) {
var el = document.createElement (tagName);
Object.keys (options).forEach (item => {
el.setAttribute (item, options[item]);
});
if (text) {
var textNode = document.createTextNode(text);
el.appendChild(textNode);
}
return el;
}
return Cvt;
})();
And in the view, we will create an instance of this object and assign it to some local variable.
In the view, I am creating a function that should create the layout of the application. As discussed earlier, we have three main components, a form, a view of a contact, and a container to render the list of contacts. So we need to divide our view into two parts,
- A form container.
- A list container.
We will add this process of creating the components in a function, your code should look like this:
// scripts/modules/contacts/contactsView.js
var ContactsView = (function () {
var template = new ContactsViewTemplates ();
function CView () {
this.formContainer = null;
this.listContainer = null;
}
/**
* @description function to create the layout of the working area
*/
CView.prototype.renderLayout = function (rootNode) {
this.formContainer = template.getElement ('div', {
'class': 'form-container'
});
this.listContainer = template.getElement ('div', {
'class': 'list-container'
});
rootNode.appendChild (this.formContainer);
rootNode.appendChild (this.listContainer);
}
return CView;
}) ();
Rendering layout will be init
// scripts/modules/contacts/contactsController.js
var ContactsController = (function () {
function CCtrl (model, view) {
this.model = model;
this.view = view;
this.rootNode = document.getElementById ('root');
init.call (this);
}
function init () {
this.view.renderLayout (this.rootNode);
}
return CCtrl;
}) ();
Note: The reason for init.call (this)
this
The contact form
We will create the contact form in one go, it is a simple task. As the function to create the form needs not to be accessed by the controller, we can create a local function in the view instead of putting it on
The following function creates the form with 4 input elements and two buttons, one to add the items, and second to clear the form.
/**
* @description function to create the contact form.
* @returns {HTMLElement}
*/
function getContactForm () {
var formBody = template.getElement ('form', {
'class': 'form-body',
'id': 'contactForm',
});
var firstName = template.getElement('input', {
'placeholder': 'Enter first name',
'type': 'text',
'class': 'form-input',
'name': 'firstName',
'id': 'firstName'
});
var lastName = template.getElement('input', {
'placeholder': 'Enter last name',
'type': 'text',
'class': 'form-input',
'name': 'lastName',
'id': 'lastName'
});
var mobile = template.getElement('input', {
'placeholder': 'Enter mobile number',
'type': 'phone',
'class': 'form-input',
'name': 'mobile',
'id': 'mobile'
});
var email = template.getElement('input', {
'placeholder': 'Enter e-mail id',
'type': 'text',
'class': 'form-input',
'name': 'email',
'id': 'email'
});
var submitButton = template.getElement('button', {
'type': 'submit',
'id': 'add'
}, 'Add');
var clearButton = template.getElement('button', {
'type': 'reset',
'id': 'clear'
}, 'Clear');
// append all fields to the form.
formBody.appendChild(firstName);
formBody.appendChild(lastName);
formBody.appendChild(mobile);
formBody.appendChild(email);
formBody.appendChild(submitButton);
formBody.appendChild(clearButton);
// at the end, return form body
return formBody;
}
Once this function is created, we can call this function inside renderLayout
function [last line].
.
.
.
rootNode.appendChild (this.formContainer);
rootNode.appendChild (this.listContainer);
rootNode.appendChild (getContactForm());
.
.
.
Adding a contact
When the user clicks on the button ‘Add’, this is what we need to do,
- Collect data from user input.
- Add it to the available list
- Update the view/list to reflect newly added data.
In a real application, the storage and sending data to the server to store in the database is done by the API provided to the client side [front-end JavaScript]. But we will not discuss how that is done, instead, we will use the localStorage API available in HTML5 to store the data locally. If you do not know what localStorage is, you should not worry about that not. You can still go ahead and consider the storage to be a black-box and work only on front-end APIs.
Create the Mock Data
For the purpose of easy understanding, let us create a mock data [hard-coded values] which we would want to display on the page. We will reuse this logic to update the list later.
Since we already know that the data comes from the model and the controller communicates with the model and view to render the data. Let us create some mock data in the model.
// scripts/modules/contacts/contactsModel.js
var ContactsModel = (function () {
function CModel () {
this.mockData = [
{
firstName: 'John',
lastName: 'Smith',
mobile: '9999999999',
email: 'john@example.com',
},
{
firstName: 'Jane',
lastName: 'Smith',
mobile: '9999999999',
email: 'jane@example.com',
},
]
}
// shared function to return mock data
CModel.prototype.getMockData = function() {
return this.mockData;
}
return CModel;
}) ();
Display the Contact List
We have the mock data, we have the container where we want to render the list, all we need to do now is to code the controller such that it gets the data from the model and displays on the screen.
Plus, this should happen during [just after] the initialisation, because when the application loads we would want to render all available contact list.
So now we know, we need a function to render the list, and we need to call this function during initialisation. So let’s move to the controller file.
Here are the steps to do this,
- The view should provide a function to render the list (view).
- The view should get the data which it needs to inject in the list, for example, the value of firstName, lastName, and so on.
- This function needs to be called inside
the init
function of the controller.
So the exact code will look like this,
// scripts/modules/contacts/contactsController.js
function init () {
this.view.renderLayout (this.rootNode);
// render the list of contact
this.view.renderList (this.model.getMockData());
}
Note: renderList
is a function available in the view [we have not written it yet], and getMockData
is a function available in the model.
Let’s go back to the view and create the renderList
function.
renderList
function accepts an array of objects.- Iterate the received list, and for each item in the array, create a row which renders the contact data.
- To create an individual row, we’ll create another function
, renderRow
, this will accept a contact object.
// scripts/modules/contacts/contactsView.js
// function to create list of items.
CView.prototype.renderList = function (list) {
// for each item of the list, render the row.
for (var index = 0; index < list.length; index++) {
renderRow (this.listContainer, list[index]);
}
}
function renderRow (listContainer, item) {
var row = template.getElement('div', {
'class': 'row-item',
});
// now, iterate over the object and create divs and then append to row.
for (var key in item) {
var val = item[key];
var span = template.getElement('span', {
'class': 'col',
}, val);
row.appendChild(span);
}
// in the end, append row to the listContainer.
listContainer.appendChild(row);
}
At this point, the page will not look great, but it renders the data that we had created in the model (mock data), and that’s what we need now.
If you noticed, the renderRow function is a private function, so we cannot call this function when we would need to add the new row. To fix this,
- we can create the renderRow function on the prototype to make it available on the object instance.
- or, we can create another function on
the prototype and then call the render function from this function.
This is a design choice, i.e. how do you want to implement it. The benefit of the second method is, renderRow
renderRow
renderRow
function.
// scripts/modules/contacts/contactsView.js
CView.prototype.createRow = function (item) {
renderRow(item);
}
As expected, createRow
function will need an object [the contact] to pass it to the renderRow
function.
In the next post, we will add the user interaction in the contact form. Initially, we will add the new data in the mock data, i.e. update the mock data. In another post, we will implement the localStorage, so if you are not sure how does it work, you work on it later as a separate topic.
Leave a Reply