V1V2
Blog & Projects

Build a simple client-side MVC app with RequireJS

RequireJS Logo

As a web developer, you certainly often started coding your JavaScript in a single file, and, as the code base gets larger and larger, it became really difficult to maintain. To solve this problem you can split your code in several files, add more script tags and use global variables to reach functions declared in other files. But this pollutes the global namespace and for each file an additional HTTP request consumes bandwidth, which slows down the loading time of the page.

If this happened to you, you certainly understand that there is a strong need to organize our front-end code differently, particularly if we have to build large-scale web apps with thousands of lines of JavaScript. We need a new way to organize all this mess to make it easier to maintain. This new technique consists in using script loaders. Plenty of them are available on the web, but we'll focus on a very good one, RequireJS.

In this step by step tutorial you will learn how to build a simple MVC (Model - View - Controller) app using RequireJS. You don't need any particular previous knowledge of script loading - we'll see the basics together.

Introduction

What is RequireJS and why it's awesome

RequireJS is an implementation of AMD (Asynchronous Module Definition), an API for declaring modules and loading them asynchronously on the fly when they're needed. It's developed by James Burke, and it just reach the symbolic 1.0 version after 2 years of development. RequireJS can help you organize your code with modules and will manage for you the asynchronous and parallel downloads of your files. Since scripts are loaded only when needed and in parallel, it reduces the loading time of your page!

MVC for the front-end?

MVC is a well known design pattern to organize server-side code and make it modular and sustainable. What about using it for front-end? Can we apply this design pattern to JavaScript? If you're just using JavaScript for animating, validating few forms or any simple treatment that doesn't require many lines of code (let's say less than 100 lines), there is no need to structure your files using MVC or to use RequireJS. But if you're building a rich web app with many different views, then definitely yes!

The app we'll create

To illustrate how to organize your MVC code using RequireJS, we'll create a very simple app with 2 views:

  • The first view will display a list of users (represented by a name attribute),
  • The second view will allow you to add a user.
Require JS MVC App

The business logic is obviously super simple so you can focus on understanding what's really important here: structuring your code. And since it's that simple, I strongly recommend that you really try to do it in parallel of reading this tutorial. It won't take long, and if you have never done modular programming or used RequireJS before, coding this example will really help you become a better programmer. Seriously, it's worth it, you should do it.

HTML and CSS files

Here is the HTML markup we'll use for this example:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>A simple MVC structure</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <div id="container">
      <h1>My users</h1>
      <nav><a href="#list">List</a> - <a href="#add">Add</a></nav>
      <div id="app"></div>
    </div>
    <script data-main="js/main" src="js/require.js"></script>
  </body>
</html>

The navigation in our app will be the links of the nav menu which will remain present on each page of the app, and all the magic of the MVC application will happen in the #app div. We also included RequireJS (which you can grab here) at the bottom of the body, and you can notice a data attribute on the script tag: data-main="js/main". This value is used by RequireJS as the entry point of the entire application.

Let's also add just a little bit of basic styling:

#container {
  font-family: Calibri, Helvetica, serif;
  color: #444;
  width: 200px;
  margin: 100px auto 0;
  padding: 30px;
  border: 1px solid #ddd;
  background: #f6f6f6;
  -webkit-border-radius: 4px;
     -moz-border-radius: 4px;
          border-radius: 4px;
}

h1, nav {
  text-align: center;
  margin: 0 0 20px;
}

OOP reminder: What is a module?

In JavaScript Object-Oriented Programming, there is a very common design pattern called Module Pattern. It is used to encapsulate methods and attributes in objects (which are the "modules") to avoid polluting the global namespace. It is also used to kind of simulate classes from other OOP languages like Java or PHP. This is how you would define a simple MyMath module in our main.js file:

var MyMath = (function () {
  // Put your private variables and functions here

  return {
    // Here are the public methods
    add: function (a, b) {
      return a + b
    },
  }
})()

console.log(MyMath.add(1, 2))

Public methods are declared using the object literal notation, which is not very convenient. You can alternatively use the Revealing Module Pattern, which returns private attributes and methods:

var MyMath = (function () {
  // With this pattern you can use the usual function notation:

  function add(a, b) {
    return a + b
  }

  return {
    add: add, // But don't forget to declare it in the returned object!
  }
})()

console.log(MyMath.add(1, 2))

I will be using the Revealing Module Pattern in the rest of this article.

RequireJS

Defining a module with RequireJS

In the last section we declared a module in a variable to call it later. This is just one way to declare modules. We're now going to see a (barely!) different method used by RequireJS. The purpose of RequireJS is to split our JavaScript files for a better maintainability, so let's create a MyMath.js file to define our MyMath module in the same folder as main.js:

define(function () {
  function add(a, b) {
    return a + b
  }

  return {
    add: add,
  }
})

Instead of declaring a variable, we just put the module as a parameter of the define function. This function is provided by RequireJS and will make our module accessible from the outside.

Requiring a module from the main file

Let's go back to our main.js file. RequireJS provides a second function called require that we're going to use to call our MyMath module. Here is what main.js now looks like:

require(['MyMath'], function (MyMath) {
  console.log(MyMath.add(1, 2))
})

The call to MyMath is now wrapped in require, which takes 2 parameters:

  • An array of modules we want to load, declared with their path relative to the entry point (remember the data-main attribute) and without the .js extension,
  • A function to call once these dependencies are loaded. The modules will be passed as parameters of this function so you can simply name those parameters with the same modules names.

You can now reload the page and... congratulations! You just called a method from an other file! Yes, that was super easy and you're now ready for the big scary MVC architecture (which works exactly like the module definition you just did, so be sure you'll be doing great!).

The MVC structure

Let's start with the part I'm sure you love: creating all the folders and files of our project. We want to use Models to represent data, the business logic will be handled by Controllers, and those controllers will call specific Views to render pages. So guess what? We need 3 folders: Models, Controllers and Views. Considering our simple app case, we'll have 2 controllers, 2 views and 1 model.

Our JavaScript folder now looks like this:

  • Controllers
    • AddController.js
    • ListController.js
  • Models
    • User.js
  • Views
    • AddView.js
    • ListView.js
  • main.js
  • require.js

Got the structure ready? Great! Let's start implementing the simplest part: the Model.

The Model: User.js

In this example, a User will be a simple class with a name attribute:

define(function () {
  function User(name) {
    this.name = name || 'Default name'
  }

  return User
})

If we now come back to our main.js file, we can declare the dependency to User in the require method, and create a set of users for the purpose of this example:

require(['Models/User'], function (User) {
  var users = [new User('Barney'), new User('Cartman'), new User('Sheldon')]

  for (var i = 0, len = users.length; i < len; i++) {
    console.log(users[i].name)
  }

  localStorage.users = JSON.stringify(users)
})

We then serialize in JSON the users array and store it in the HTML5 local storage to make it accessible just like a database:

Chrome Console showing the users

Displaying the users list

It's time to display those users in the app interface! We'll have to work with ListController.js and ListView.js to do that. Those 2 components are obviously related, and they'll be linked somehow. There are many ways we can do that, and to keep this example simple, here is what I suggest: The ListView will have a render method and our ListController will simply get the users from the local storage and call ListView's render method by passing the users as a parameter. So obviously, ListController needs ListView as a dependency.

Just like with require, you can pass an array of dependencies to define if the module relies on other modules. Let's also create a start method (or any other name that makes sense to you - like run or main), to put the main behavior of the controller in it:

define(['Views/ListView'], function (ListView) {
  function start() {
    var users = JSON.parse(localStorage.users)
    ListView.render({ users: users })
  }

  return {
    start: start,
  }
})

Here we deserialize the users from the local storage and pass it to render as an object. Now, all we have to do is implementing a render method in ListView.js:

define(function () {
  function render(parameters) {
    var appDiv = document.getElementById('app')

    var users = parameters.users

    var html = '<ul>'
    for (var i = 0, len = users.length; i < len; i++) {
      html += '<li>' + users[i].name + '</li>'
    }
    html += '</ul>'

    appDiv.innerHTML = html
  }

  return {
    render: render,
  }
})

This method simply loops on our users to concatenate them in an HTML string we inject in the #app element.

Now, all we need to do is to "run" our ListController module. To do that let's declare it as a dependency of require in our main.js file, and call ListController.start():

require(['Models/User', 'Controllers/ListController'], function (User, ListController) {
  var users = [new User('Barney'), new User('Cartman'), new User('Sheldon')]

  localStorage.users = JSON.stringify(users)

  ListController.start()
})

You can now refresh your page to get this wonderful list of users:

MVC users list

Woooaaaah, it works! Congratulations if you coded this too!

Adding a user

We now want to be able to add users to our list. We'll display a simple text input and a button, with an event handler when the button is clicked to add the user to the local storage. Let's start with AddController, just like we did in the previous section. This file will be pretty simple since we don't have any parameter to give to the view. Here is AddController.js:

define(['Views/AddView'], function (AddView) {
  function start() {
    AddView.render()
  }

  return {
    start: start,
  }
})

And its corresponding view:

define(function () {
  function render(parameters) {
    var appDiv = document.getElementById('app')
    appDiv.innerHTML = '<input id="user-name" /><button id="add">Add this user</button>'
  }

  return {
    render: render,
  }
})

You can now declare AddController as a dependency in your main file and call its start method to successfully get the expected view:

MVC Add user

But since we don't have any event bind on the button yet, this view is not very useful... Let's work on that. I have a question for you: Where should we put the event logic for this event? In the view? The controller? If we put it in the view it would be the right place to add events listeners, but putting the business logic in a view would be a bad practice. Putting the event logic in the controller seems to be a better idea, even if it's not perfect because we don't want to see any div ID in there, which belong to the view.

As I said, let's put all the event logic in the controller. We can create a bindEvents function in AddController and call it after the view has finished rendering the HTML:

define(['Views/AddView', 'Models/User'], function (AddView, User) {
  function start() {
    AddView.render()
    bindEvents()
  }

  function bindEvents() {
    document.getElementById('add').addEventListener(
      'click',
      function () {
        var users = JSON.parse(localStorage.users)
        var userName = document.getElementById('user-name').value
        users.push(new User(userName))
        localStorage.users = JSON.stringify(users)
        require(['Controllers/ListController'], function (ListController) {
          ListController.start()
        })
      },
      false
    )
  }

  return {
    start: start,
  }
})

In bindEvents, we simply add an event listener for clicks on the #add button (feel free to use your own function to deal with IE's attachEvent - or just use jQuery). When the button is clicked, we get the users string from the local storage, deserialize it to get the array, push a new user with the name contained in the #user-name input field, and put the updated users array back to the local storage. After that we finally require the ListController to execute its start method so we can see the result:

MVC List - User added

Brilliant! It's time for you to take a break, you've done a very good job if you're still doing this example with me. You deserve a cup of coffee before we continue!

Navigation between views with routes

Okay back to work. Our little app is pretty cool but it really sucks that we still cannot navigate between views to add more users. What is missing is a routing system. If you've worked with server-side MVC frameworks before, you're probably familiar with this. Each URL leads to a different view. However here we're client-side and it's slightly different. JavaScript single page interfaces like this one use the hash of the URL to navigate between different parts of the app. In our case, we want to be able to reach the 2 different views when hitting those URLs:

  • http://yourlocalhostpath/#list
  • http://yourlocalhostpath/#add

This will make each page of our app bookmarkable and easily reachable.

Browsers compatibility and functioning

Managing history and hash navigation can be very painful if you need a good compatibility with old browsers. Depending on the browsers you aim to support here are different solution you can consider:

In our case we will do the simple manual monitoring, which is pretty easy to implement. All we need to do is checking if the hash changed every n milliseconds, and fire some function if a change is detected.

The routes and the main routing loop

Let's create a Router.js file next to main.js to manage the routing logic. In this file we need a way to declare our routes and the default one if none is specified in the URL. We can for instance use a simple array of route objects that contain a hash and the corresponding controller we want to load. We also need a defaultRoute if no hash is present in the URL:

define(function () {
  var routes = [
    { hash: '#list', controller: 'ListController' },
    { hash: '#add', controller: 'AddController' },
  ]
  var defaultRoute = '#list'
  var currentHash = ''

  function startRouting() {
    window.location.hash = window.location.hash || defaultRoute
    setInterval(hashCheck, 100)
  }

  return {
    startRouting: startRouting,
  }
})

When startRouting will be called, it will set the default hash value in the URL, and will start a repetition of calls to hashCheck that we haven't implemented yet. The currentHash variable is used to store the current value of the hash.

Check for hash changes

And here is hashCheck, the function called every 100 milliseconds:

function hashCheck() {
  if (window.location.hash != currentHash) {
    for (var i = 0, currentRoute; (currentRoute = routes[i++]); ) {
      if (window.location.hash == currentRoute.hash) loadController(currentRoute.controller)
    }
    currentHash = window.location.hash
  }
}

hashCheck simply checks if the hash has changed, and if it matches one of the routes, calls loadController with the corresponding controller name.

Loading the right controller

Finally, loadController just performs a call to require to load the controller's module and execute its start function:

function loadController(controllerName) {
  require(['Controllers/' + controllerName], function (controller) {
    controller.start()
  })
}

So the final Router.js file looks like this:

define(function () {
  var routes = [
    { hash: '#list', controller: 'ListController' },
    { hash: '#add', controller: 'AddController' },
  ]
  var defaultRoute = '#list'
  var currentHash = ''

  function startRouting() {
    window.location.hash = window.location.hash || defaultRoute
    setInterval(hashCheck, 100)
  }

  function hashCheck() {
    if (window.location.hash != currentHash) {
      for (var i = 0, currentRoute; (currentRoute = routes[i++]); ) {
        if (window.location.hash == currentRoute.hash) loadController(currentRoute.controller)
      }
      currentHash = window.location.hash
    }
  }

  function loadController(controllerName) {
    require(['Controllers/' + controllerName], function (controller) {
      controller.start()
    })
  }

  return {
    startRouting: startRouting,
  }
})

Using the new routing system

Now we can require this module from our main file and call startRouting:

require(['Models/User', 'Router'], function (User, Router) {
  var users = [new User('Barney'), new User('Cartman'), new User('Sheldon')]

  localStorage.users = JSON.stringify(users)

  Router.startRouting()
})

To navigate in our app from a controller to another, we can just replace the current window.hash with the new controller's hash route. In our case we still manually load the ListController in AddController instead of using our new routing system:

require(['Controllers/ListController'], function (ListController) {
  ListController.start()
})

Let's replace those 3 lines with a simple hash update:

window.location.hash = '#list'

And this is it! Our app now has a functional routing system! You can navigate from a view to another, come back, do whatever you want with the hash in the URL, it will keep loading the right controller if it matches the routes declared in the router. Sweet!

Conclusion

You can be proud of yourself, you built an entire MVC app without any framework! We just used RequireJS to link our files together, which is really the only mandatory tool to build something modular. So now what are the next steps? Well if you liked the minimalist Do It Yourself approach I had in this tutorial, you can enrich our little framework with new features as your app grows and has new technical needs. Here are some ideas of potential interesting next steps:

  • Integrate a templating system,
  • Create a small external library to put all what is not purely related to your app (like the routing system) to be able to reuse it on other projects,
  • Define what a Model, a View and a Controller are with objects in this library,
  • Create a new abstraction layer to deal with getting the data from various sources (RESTful APIs, localStorage, IndexedDB...).

This DIY approach is excellent to learn, but the current state of our framework is really not sufficient for real life projects at this point. If you're too lazy to implement the features listed above (and this list is absolutely not exhaustive!), you can also start learning how to use an existing MVC framework. The most popular are:

I personally like Backbone because it's lightweight (less than 5kb minified), so my next tutorial will be about mixing RequireJS and Backbone, which works pretty well! Follow @verekia for updates about this. You should also follow Addy Osmani and read his tutorials about large scale JavaScript applications and modular JavaScript, and James Burke the creator of RequireJS. Both are very good sources of information for building modular JavaScript apps. Addy Osmani also started a project called TodoMVC to compare how different MVC frameworks can be used to build the same simple web app, which is really helpful to choose the right framework for your needs.

That's all for today folks, thanks for reading!