Firefox OS Contact App Using Vanilla JavaScript

This tutorial is about creating Firefox OS contact viewer app in vanilla JavaScript (without using any library or framework). We will be creating app using some best JavaScript practices which could be applied to SPA (Single Page Application), and Phonegap apps as well. We will also make use of Web Components to develop a custom element which will help us in layout and its animations. You will find this tutorial useful if you write JavaScript. The only thing that’s specific to Firefox OS will be the use of Contact WebAPI.

Source Code

You can find source on github along with the instructions to run it in Firefox OS simulator.


xView Layout Component

Before we start writing our app, let’s create xView custom element that will help us in our app layouts/views so we don’t need to worry about each view’s CSS. It will also help us in animating the views using simple API. So in our app all the layout and animation code will be abstracted in this custom element. The end result of this will be very similar to AngularJS directive but without the use of any framework.

There are lot of articles introducing Web Components. So instead of some introduction, let’s dive in and create a practical web component which can help us in single page app and mobile layouts/views. Also it will help us in view related animations e.g. slide in or slide out the views.


xView Usage

Instantiating xView

Before implementing xView custom element, let’s see how we can use it. There are two ways with which we can create xView element like any other HTML element. First is to simply declare it in HTML.

<x-view>
  <header>My App</header>
  <section>Body goes here...</section>
  <footer>Copyright</footer>
</x-view>

Second way is to create our custom element using JavaScript.

var xv = new xView();
document.querySelector('#container').appendChild(xv);

Animating xView

Suppose you want to show xView by sliding in. First you need to hide it using CSS

<x-view id="my-view" style="display: none">
  ...
</x-view>

Then in JavaScript.

var mv = document.querySelector('#my-view');
mv.show('slide-left-in');

Similarly to hide the view.

mv.hide('slide-left-out');

The animation names e.g. slide-left-in that we pass to show/hide methods are actually CSS based animations.


xView Implementation

JavaScript

Make a component folder in the root directory of the project and add x-view.js file in it. This is where all our JavaScript code will go. document.registerElement is used to create a new custom element.

var xView = (function () {
  var xViewProto = Object.create(HTMLElement.prototype);
 
  return document.registerElement('x-view', {
    prototype: xViewProto
  });
 
}());

The first parameter in registerElement is the name which we want to set for our element. Second parameter is an object with property specifying prototype from which it will inherit. All custom elements inherit from HTMLElement‘s prototype by default. But in above code it’s explicitly defined prototype: xViewProto because some custom methods will be defined on the xViewProto object as shown below.

// var xViewProto = ...
 
xViewProto.createdCallback = function () {
 
  // here "this" points to newly created xView element
 
  this._aniQ = [];
 
  this.addEventListener('animationend', function (e) {
    if (this.dataset.hide) {
      this.style.display = 'none';
    }
 
    clearAniQ(this);
  }, false);
 
};
 
// return document.registerElement ...

There are some callbacks which you can create on prototype object. Above method createdCallback will be called whenever a new instance of xView element is created. Within the body of createdCallback we are creating an array _aniQ which will be used to store animations added on this element.

Then an event handler is added for animationend event. This event fires whenever animation is completed on the DOM element. Within the handler data-hide attribute is checked (using dataset.hide) and hide the element if it’s present. Then clearAniQ is called with this (current xView element) as parameter. This function will remove the CSS animation classes from the element with _aniQ array’s help.

The attribute data-hide will be added in hide method (defined later) so that the DOM element could be hidden in above animationend handler after the animation completes. Now let’s define clearAniQ function.

function clearAniQ(xv) {
  // classList.remove is called using apply to avoid
  // loop over array _aniQ and removing classes ony-by-one
  xv.classList.remove.apply(xv.classList, xv._aniQ);
 
  xv._aniQ.length = 0;
}

Note that this function is not defined on xViewProto. It’s used to remove all the corresonding CSS animation classes present in _aniQ array from the element and then the array is emptied. CSS classes are removed based on _aniQ array. This is done so the future animations could work properly when new CSS animation classes were applied.

Let’s define show and hide methods on xViewProto object.

xViewProto.show = function (aniType) {
  var self = this;
 
  if (!aniType) return;
 
  self.style.display = '';
  delete self.dataset.hide;
  addAni(self, aniType);
};
 
 
xViewProto.hide = function (aniType) {
  var self = this;
 
  if (!aniType) return;
 
  self.dataset.hide = true;
  addAni(self, aniType);
};

Both show/hide methods return if no animation type aniType is passed as parameter. In show method we are clearing the display property so that if it was previously hidden could be revealed. Then data-hide attribute is removed because we don’t want to hide it after animation ends. Finally the animation starts by calling addAni function (defined later).

The hide method adds data-hide attribute by setting dataset.hide to true so the element could be hidden after animation ends. Then the animation starts using addAni function. Here comes the addAni function.

function addAni(xv, aniType) {
  xv.classList.add(aniType);
  xv._aniQ.push(aniType);
}

addAni is pretty simple. All it does is fire the animation by adding the CSS class on the element and then push it on the _aniQ. You can check the complete source code of x-view.js here.

CSS

Create x-view.css file under the same component folder where x-view.js file was created. Then add following CSS in it. You can find the CSS source here.

x-view {
  display: flex;
  flex-direction: column;
  position: fixed !important;
  top: 0; right: 0; bottom: 0; left: 0;
  overflow: hidden;
}
 
x-view > header,
x-view > footer {
  text-align: center;
}
 
x-view > section {
  flex: 1;
  overflow: auto;
}
 
 
/**
 * x-view animations
 */
x-view.slide-left-in {
  animation: slide-left-in .5s;
  transform: translateX(0%);
}
x-view.slide-left-out {
  animation: slide-left-out .5s;
  transform: translateX(-100%);
}
x-view.slide-right-in {
  animation: slide-left-out .5s reverse;
  transform: translateX(0%);
}
x-view.slide-right-out {
  animation: slide-left-in .5s reverse;
  transform: translateX(100%);
}
x-view.fade-in {
  animation: fade-in .5s;
  opacity: 1;
}
x-view.fade-out {
  animation: fade-in .5s reverse;
  opacity: 0;
}
 
@keyframes slide-left-in {
  0% { transform: translateX(100%); }
  100% { transform: translateX(0%); }
}
 
@keyframes slide-left-out {
  0% { transform: translateX(0%); }
  100% { transform: translateX(-100%); }
}
 
@keyframes fade-in {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

App Manifest

Now we have created our layout web component. Let’s use it in our contact viewer app. The first step is to create an app manifest file manifest.webapp in the root directory of project.

<code>{
  "name": "FxContactMgr",
  "description": "Firefox OS Contact Viewer",
  "launch_path": "/index.html",
  "icons": { "128": "/img/icon-128.png" },
  "developer": {
    "name": "Fawad Hassan",
    "url": "http://ifadey.com"
  },
  "type": "privileged",
  "permissions": {
    "contacts": {
      "access": "readwrite",
      "description": "Required for contact viewer app"
    }
  }
}</code>

It contains some general info like app name, description, developer info, etc. Let me explain some important properties in above JSON.

launch_path is the relative (to project root folder) path of the HTML file which will get launched when user tap on app icon.

type property is used to define the type of app we are creating. Since the app will get installed on user’s device and it will access sensitive Contacts API, so its type must be privileged. Take a look at this doc if you don’t know about Firefox OS app types.

Whenever any sensitive API is required, a permission must be added under permissions property. In our case the app will access Contacts API so a permission of contacts is added with the access information readwrite and description where we define for what purpose this API is used in the app.


HTML File

Create an index.html file in the root folder of project. This is the file whose path was given in launch_path.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
  <title>Firefox Contact Viewer</title>
</head>
<body>
 
</body>
</html>

Init and Bootstrap

First create a js folder under the root of project folder. Then create namespace.js file in the js folder. In this file some namespaces are initialized where we will add relevant code.

window.FxContactMgr = {};
window.FxContactMgr.API = {};
window.FxContactMgr.View = {};

Some of you may add these lines in inline script but remember inline scripts don’t work for Firefox OS apps. I extremely recommend you to read Apps CSP article on Mozilla documentation website. This will help you avoid hours of debugging for stupid issues.

Create another file main.js under js folder and add following code:

(function () {
  'use strict';
 
  document.addEventListener('WebComponentsReady', function (e) {
    console.log('main.js');
    window.FxContactMgr.View.ContactList.render();
  }, false);
 
}());

Note that here an event listener is added for WebComponentsReady event. This event is required by custom-elements.js polyfill which I took from x-tags project. It’s required to make sure the DOM element gets upgraded according to the custom element we wrote. Otherwise we maybe dealing with unknown HTML element.

Within the event handler ContactList view is shown by calling render method. This will bootstrap our app by showing a view containing contact list. We will create ContactList view later.


Contacts API

Create api folder under js folder. Then create contacts.js file and add API.Contacts object where we will create method getAllContacts which will allow us to load contacts in chunks and fire callback for each loaded chunk.

window.FxContactMgr.API.Contacts = {
 
  getAllContacts: function (chunkSize, cb) {
    var self = this,
        contacts = [],
        count = 0,
        cursor = navigator.mozContacts.getAll({sortBy: "givenName", sortOrder: "ascending"});
 
    cursor.onsuccess = function (e) {
      var contact = e.target.result;
      if (contact) {
        contacts.push(self.transformContact(contact));
 
        if (++count === chunkSize) {
          cb(contacts);
          count = 0;
          contacts = [];
        }
 
        cursor.continue();
      } else {
        cb(contacts);
      }
    };
 
    cursor.onerror = function () {
      cb(null);
    };
  } //end getAllContacts
 
};

getAllContacts accept two parameters. chunkSize is the number of contacts to be loaded before calling callback cb passed as second parameter.

Within the body, we have some variable declarations. self is assigned to this which in our case will point to the object in which getAllContacts is placed.

Then comes contacts array which will contain list of contacts with limit of chunkSize. count is used to keep track of contacts added in contacts array so far. Last one is cursor which make use of Firefox OS contacts api getAll which returns a cursor based on the query passed to it as an object. The query we are using is to sort the contacts with givenName (first name) and then defining the sort order as ascending.

Then two event handlers (success and error) are attached to cursor. Within the success handler, e.target.result contains the current contact that’s fetched. Then the contact variable is checked to see if something exists in it. If no, it means the cursor has reached its end so simply call cb with contacts passed to it. If yes then push the current contact to contacts array. But before pushing it to array, we are transforming the current contact using transformContact method which we will define soon in the same object.

Then a check is added to see if the count reaches the limit of chunkSize. In case of true the cb is called with contacts loaded so far as parameter. Then count and contacts is reset to track a new chunk.

Next contact is not fetched until we call cursor.continue() method. After this call the same event handlers fire for the next contact. In other words Firefox OS contacts api fetches contacts one-by-one instead of getting it in bulk.

Now let’s add transformContact method that we used in getAllContacts.

transformContact: function (c) {
  return {
    fname  : c.givenName  ? c.givenName[0]  : '',
    lname  : c.familyName ? c.familyName[0] : '',
 
    //both tel and email properties are arrays and
    //may contain more than one number/email.
    //For simplicity I am using only first one
    mobile : c.tel && c.tel.length     ? c.tel[0].value    : '',
    email  : c.email && c.email.length ? c.email[0].value  : ''
  };
}

Here we are simply transforming the Firefox OS specific contact properties to some other format and return a new object.

You can check the complete source of contacts.js file here.


Contact List View

Contact List View

HTML

Coming back to index.html file that we created earlier. Add HTML for the view where list of contacts will be displayed as shown below:

<x-view id="view-contacts" style="display: none">
  <header>
    <h1>Contacts</h1>
  </header>
 
  <section class="view-content">
    <ul id="contacts-list" class="list reset-list">
      <!--
        === This is how the rendered template look like ===
        <li class="listitem" data-idx="0">Fawad Hassan</li>
      -->
    </ul>
  </section>
</x-view><!--end view-contacts-->

You can understand the structure of #view-contacts from above HTML. Note that we make use of x-view element that we created earlier. Specially take a look at commented example of how each list item will look after fetching the contacts and rendering it. Let’s define a template for this list item.

<template id="tmpl-contact-item">
  <li class="listitem"></li>
</template>

This way of creating templates was introduced in web compoenents spec. Nothing special in above code. Simple template containing li element with listitem class.

JavaScript

Initialization

Create a view folder under js folder. This is where all our views will go. Then create a js/view/contacts-list.js file and add following JavaScript in it.

window.FxContactMgr.View.ContactList = (function () {
  'use strict';
 
  const CHUNK_SIZE = 20;
 
  var exports = {},
      ContactsAPI = null,
      ContactDetailsView = null,
 
      ui = {},
      tmplContactItem = null,
      cachedContacts = [],
      isFirstChunk = true,
      renderedTill = 0;
 
 
  function init() {
    ContactsAPI = window.FxContactMgr.API.Contacts;
    ContactDetailsView = window.FxContactMgr.View.ContactDetails;
 
    //--- cache dom elements ---//
    ui.view = document.querySelector('#view-contacts');
    ui.viewContent = ui.view.querySelector('.view-content');
    ui.contactList = ui.view.querySelector('#contacts-list');
    tmplContactItem = document.querySelector('#tmpl-contact-item').content;
 
    //--- add event listeners ---//
    /**
     * always use touch events instead of click when developing for
     * touch screens. That way you can eleminate 300ms delay in your
     * touch/click events http://addr.pk/ae631
     */
    ui.contactList.addEventListener('click', contactClickHdlr, false);
    ui.viewContent.addEventListener('scroll', contentScroll, false);
 
    //--- load and render contact list ---//
    ContactsAPI.getAllContacts(CHUNK_SIZE, getContactsCb);
 
  } //end init
 
}());

Variable definitions are explained below:

  1. CHUNK_SIZE: A constant containing the number of contacts to be rendered when scrollbar is near the end.
  2. exports: An object which will be returned from this module and it will contain the properties that need to made public.
  3. ContactsAPI: Contain a reference to object which was created eariler in js/api/contacts.js file. It contain methods that interact with contacts API. This variable is initialized in init function (line 1 of init body).
  4. ContactDetailsView: Contain reference to ContactDetails view. Initialized on line 2 of init body. This view is now created yet.
  5. ui: An object where all DOM elements related to current view are cached.
  6. tmplContactItem: A reference to #tmpl-contact-item template that we created earlier.
  7. cachedContacts: An array containing contacts fetched from phone using contacts api.
  8. isFirstChunk: A flag used to check whether the loaded chunk of contacts is the first or not.
  9. renderedTill: Stores the index for array cachedContacts to keep track of contacts rendered so far on the screen.

In init function, some initialization work is done. The first two lines in init body are storing reference to other external modules (ContactsAPI and ContactDetailsView). Next comes the caching of DOM elements that are relevant to this view. Then two event listeners are attached which are not yet written and a call to ContactsAPI.getAllContacts method which we created earlier. It provide contacts in chunks and for each loaded chunk, it will call getContactsCb function.

After looking init function, we need to implement three functions. contactClickHdlr, contentScroll, and getContactsCb. Before implementing these functions, lets add render function which will be made public later so that other modules can call it.

Render View

function render() {
  console.log('init...');
 
  exports.render = function () {
    console.log('render contact list...');
    ui.view.show('fade-in');
  };
 
  init();
  exports.render();
} //end render
 
exports.render = render;
return exports;

Before taking a look at render body, note the last two lines where render function is made public by adding it in exports object. Place these two lines at the end of module. Take a look at complete source here if you are confused.

Within render function, exports.render is reassigned with a new function. This will replace the original reference to render function with this inner function. Then init function is called which we created earlier and the newly assigned function is called using exports.render() call.

This pattern allows us to separate init related work and the code which we want to execute each time after first call. When using this technique, we don’t have to write useless if condition which checks each time the function call is first or not.

Within the body of inner function, ui.view.show method is called which will show the current view using fade-in animation. If you remember, ui.view contains the #view-contacts DOM element and show method is available on it because we are using custom element x-view in which we added two methods (show/hide).

Get Contacts Callback

Coming back to function which is not implemented yet but used in init function. In init function, getContactsCb was passed as callback to ContactsAPI.getAllContacts method which gets called for each chunk of contacts loaded. Here’s the definition of getContactsCb.

function getContactsCb(contacts) {
  if (!contacts) {
    alert('Failed to load contacts');
    return;
  }
 
  cachedContacts = cachedContacts.concat(contacts);
 
  if (isFirstChunk) {
    renderContacts();
    isFirstChunk = false;
  }
 
} //end getContactsCb

This function gets contacts array as parameter. It contains CHUNK_SIZE i.e. 20 number of contacts in it each time. Then a check is made to see if contacts contain null value in case an error occured in ContactsAPI.getAllContacts. In such a case alert is shown with failure message and control is returned.

In case of success, the contacts array is concatenated with cachedContacts to store the newly arrived contacts in cachedContacts.

Then isFirstChunk variable is checked to see if this is the first time callback is called. If yes then first set (CHUNK_SIZE) of contacts are rendered using renderContacts function which will be defined soon. In the same if condition body, isFirstChunk variable is set to false after first call.

So basically all we are doing here is caching contacts and rendering some of them if this callback is called first time.

Render Contacts

Let’s define the rendering function which renders CHUNK_SIZE of contacts from cachedContacts array.

function renderContacts() {
  var listFragment = document.createDocumentFragment(),
      i,
      len = cachedContacts.length,
      limit = renderedTill + CHUNK_SIZE;
 
  for (i = renderedTill; i < len && i < limit; ++i) {
    listFragment.appendChild(renderContact(cachedContacts[i], i));
  }
 
  ui.contactList.appendChild(listFragment);
  renderedTill = i;
}

In above function, the loop which iterates over cachedContacts array starts from renderedTill index. Each time a chunk is rendered from cachedContacts, the index where previous call to renderContacts left rendering is stored in renderedTill variable (check the last line of above function body). So the rendering continues/start from renderedTill index and ends if the current index i has reached the end of cachedContacts or if the current index i has crossed the limit i.e. 20. The limit is calculated by adding renderedTill and CHUNK_SIZE.

One important thing to note here is the use of documentFragment. Think of it as a virtual DOM element. It exist only in memory and has no appearance on the page. On each loop iteration, the DOM element returned from a renderContact (helper function which we will define next) is appended to listFragment (documentFragment) instead directly appending it to ui.contactList. This is done for performance reasons. Interacting with DOM is expensive so to avoid DOM access repeatedly, listFragment is used. After the loop this listFragment is appended to ui.contactList in one go. Remember it will append the child elements of listFragment. In the end the index where for loop ends is stored in renderedTill.

Render Contact Helper

The helper function renderContact that’s used in renderContacts function simply creates a DOM element for the contact whose info is passed as parameter.

function renderContact(contact, idx) {
  var li = tmplContactItem.cloneNode(true).querySelector('li.listitem');
  li.dataset.idx = idx;
  li.textContent = contact.fname + ' ' + contact.lname;
  return li;
}

First this function make a copy of li element from template. Then the passed index idx is stored in data-idx attribute. Remember this index is the position of contact object in cachedContacts array. Then the text is added in the newly cloned li element and finally return it.

Rendering Additional Contacts On Scroll

To remind you, there were three functions which were not implemented but used in init function. One of them is was getContactsCb which we have now implemented in addition to its helper functions renderContacts, and renderContact. What the help of these function, we have cached the contacts and rendered only the first chunk. What about rest of the contacts?

The second function which we will implement now is contentScroll. It was basically a scroll event handler which was assigned to ui.viewContent (container of main content). This is where we will load additional contacts from cachedContacts array when the scroll reaches near the end.

function contentScroll(e) {
  var viewContent = ui.viewContent;
 
  // scrolled so far + height of container >= total scroll height
  if ((viewContent.scrollTop + viewContent.offsetHeight) >= viewContent.scrollHeight) {
    //load more contacts
    renderContacts();
  }
}

There’s nothing complicated in this handler. The if condition checks whether the scroll has reached near end, if yes then render the next chunk of contacts using renderContacts function defined earlier. The important thing here is to understand the if condition.

scrollTop property gives us the scrolled value in pixels from the top. offsetHeight is the height of viewContent including border width and padding. scrollHeight is the total height of scrollable area. Within the condition scrollTop is added to offsetHeight to make the condition true when the scrollbar has reached near the end. If we don’t add offsetHeight, the condition will be true only if the scrollbar is reached to the end (not near) of scrollHeight. This is done to prefetch the contacts before reaching the scroll end.

Showing Contact Details

contactClickHdlr function is the last one (used in init function) which require implementation. This is where contact details are opened when user tap/click on the contact. So basically this is a tap/click handler.

function contactClickHdlr(e) {
  var idx = e.target.dataset.idx;
  console.log(cachedContacts[idx]);
  ui.view.hide('fade-out');
  ContactDetailsView.render(cachedContacts[idx]);
}

First it get the index of contact on which user tapped. Then the current view (contact list view) is made hidden using fade-out animation. The last line show the contact details view by passing selected contact from cachedContacts to render. Next we will write contact details view.


Contact Details View

Contact Details View

HTML

Add following HTML in index.html file under contact list view markup.

<x-view id="view-contact-details" style="display: none">
  <header>
    <button class="back left">&#10094;</button>
    <h1>Contact Details</h1>
  </header>
 
  <section class="view-content">
    <p>
      <strong class="lbl">Name:</strong>
      <span class="val name">Fawad Hassan</span>
    </p>
 
    <p>
      <strong class="lbl">Mobile:</strong>
      <span class="val mob">+92-333-1234567</span>
    </p>
 
    <p>
      <strong class="lbl">Email:</strong>
      <span class="val email">fawad@ifadey.com</span>
    </p>
  </section>
</x-view><!--end view-contacts-details-->

Lot of contact information could be stored/retrieved in Firefox OS using Contact WebAPI. For simplicity, we will be showing only contact’s name, number, and email. So in above markup, placeholders for these three values are created.

JavaScript

Initialization

window.FxContactMgr.View.ContactDetails = (function () {
  'use strict';
 
  var exports = {},
      ContactListView = null,
      ui = {};
 
 
  function init() {
    ContactListView = window.FxContactMgr.View.ContactList;
 
    //--- cache dom elements ---//
    ui.view = document.querySelector('#view-contact-details');
    ui.btnBack = document.querySelector('#view-contact-details > header > .back');
    ui.valName = document.querySelector('#view-contact-details .val.name');
    ui.valEmail = document.querySelector('#view-contact-details .val.email');
    ui.valMob = document.querySelector('#view-contact-details .val.mob');
 
    //--- add event listeners ---//
    /**
     * always use touch events instead of click when developing for
     * touch screens. That way you can eleminate 300ms delay in your
     * touch/click events http://addr.pk/ae631
     */
    ui.btnBack.addEventListener('click', closeView, false);
 
  } //end init

The variable declarations are very similar to what we did in contact list view. The only new variable is ContactListView in which reference of contact list view is assigned in init function. Rest of them (exports and ui) have same purpose as explained in contact list view.

In init function, after assigning reference to ContactListView, some DOM elements are cached which will be used later by other functions. Then a click handler closeView on btnBack (back button in header) is attached. In closeView we will hide currently opened contact details view using some animation and show the contact list view.

Render View

function render(contact) {
 
  exports.render = function (contact) {
    console.log('render contact details...');
 
    ui.valName.textContent = contact.fname + ' ' + contact.lname;
    ui.valMob.textContent = contact.mobile;
    ui.valEmail.textContent = contact.email;
 
    ui.view.show('slide-left-in');
  };
 
  init();
  exports.render(contact);
} //end render

render function just like in contact list view assign a new inner function to exports.render so in next call to render function will actually execute the newly reassigned function to avoid init related work. Note this render function accepts contact as parameter whose details we want to show. Remember we passed this contact object from contact list view.

Within the inner render function, we are simply assigning text values of name, mobile, and email based on contact object passed to it. These values are assigned to relevant DOM elements and then the details view is shown using slide-left-in animation.

Close Details View

function closeView() {
  ui.view.hide('slide-left-out');
  ContactListView.render();
}

Nothing complicated here. Details view is hidden using slide-left-out animation and list view is made visible using its render method.


CSS

The CSS of this app is not that complicated and easy to understand. You can find CSS for this app here.


Conclusion

Getting started with Firefox OS apps is pretty easy specially for those who already develop apps using HTML5. Basically you use your existing skill set of single page apps. Other great thing about Firefox OS is HTML5 is first class citizen or in other words HTML5 apps are native and you don’t need any SDK. Also if you develop open web app using Firefox OS WebAPIs, you can run it on multiple platforms like Firefox OS, Desktop (Linux, Windows, OS X), Android, and Tizen (with some modifications). Now this is a huge plus.

You may have noticed that creating an app using vanilla JavaScript is not that difficult as most of us think. Thanks to the rapid evolution in JavaScript and DOM API. So think before using large frameworks that you really need them for your average project. Also do take a look at Gaia (UI layer of Firefox OS) source code which is written in JavaScript. You can learn a lot from it.

If you found this post useful then don’t forget to subscribe for updates using RSS or Email.


comments powered by Disqus