ViewsHandler - a framework for DOM-based Applications

During a recent course on DOM scripting the attendees realized and complained quite quickly that writing large applications with HTML generated by DOM methods can easily result in bloated, unreadable code. This is why we set up ViewsHandler which is a small JavaScript framework that makes this task a bit easier.

Download ViewsHandler and demos (version 1.0)

The ideas of ViewsHandler

ViewsHandler is based on a few assumptions about JavaScript applications:

  • We want to progressively enhance a small amount of HTML that provides the same functionality should JavaScript not be available
  • We create an application that has one "canvas" (say a DIV element) and different "views" or states of the application
  • Each of these views has a large amount of HTML that needs to be created, but on the other hand only a small amount of elements that really get changed dynamically during interaction with the application

Say you want for example to show a feed of photos from flickr.com. An application doing exactly that is provided as one of the examples of ViewsHandler. The steps to do that would be:

  • provide a link pointing to the flickr page that shows the same feed
  • grab the link url, convert it to an API call and retrieve the photo data
  • create the shell of the application and different views of it:
    • The introduction view with all the thumbnails
    • The detail view with the information of a single photo
    • The info view with information about the feed
  • Populate the introduction view with the right data and display it
  • When a user clicks a thumbnail, populate the detail view and show it whilst hiding the earlier visible view
  • Provide a menu or buttons to switch in between views

Setting up the application view controller

The first step to create an application like this is to create the main HTML template including viewsHandler and the shell to be populated by it. This shell contains the fallback link that gets progressively enhanced. You should also start your application (in this case using a module pattern):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
        }
      }
    }
 }();
 myApp.init();
</script>
</body>
</html>

Adding the views definitions

Next, you can start creating your views for viewsHandler. This is a simple object with properties for each view and a pointer to a create method for each view. We call it views here, but you can choose any name:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
        }
      }
    }
 }();
 myApp.init();
</script>
</body>
</html>

In each of the create methods you need to set up the HTML that will be shown in the view. Simply create any HTML you want and leave empty placeholders for the real data.

In each create method you use the following ViewsHandler methods to populate your views and define the small sections of HTML that will be replaced by real data later on:

viewsHandler.define(view,label,element)
defines element as label for view
viewsHandler.add(view,element)
adds the element to the view

In other words, you append the newly created HTML to the DIV that represents view with viewsHandler.add() and you define a shortcut to this piece of HTML with viewsHandler.define().

This means there'll be no expensive DOM manipulation and changes after this step - ViewsHandler makes sure that your application has both the generated HTML and the pointer to it already in memory.

Let's say that in your index view you want a heading that gets changed every time you have new data for it. The following creates the heading element, adds it to the index view and defines it as a label:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    function createIndex(){
      var heading = document.createElement('h1');
      viewsHandler.add('index',heading);
      viewsHandler.define('index','heading',heading);
    };
    function createDetail(){
      // set up HTML for the detail view
    };
    function createInfo(){
      // set up HTML for the info view
    };
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
        }
      }
    }
 }();
 myApp.init();
</script>
</body>
</html>

However, none of that will be executed yet - for the shell to get the different view DIVs and your create methods to be called you need to call viewsHandler.create(). This method takes two parameters: the reference to the element that is your application shell and your views object. In this case this is the canvas reference and the views object:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    function createIndex(){
      var heading = document.createElement('h1');
      viewsHandler.add('index',heading);
      viewsHandler.define('index','heading',heading);
    }
    function createDetail(){
      // set up HTML for the detail view
    }
    function createInfo(){
      // set up HTML for the info view
    }
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
          viewsHandler.create(canvas,views);
        }
      }
    }
 }();
 myApp.init();
</script>
</body>
</html>

You'll notice that this creates the views, but also shows the first one. this is because ViewsHandler does not hide the views in JavaScript but allows you to define the way of hiding in CSS. All hidden views get a CSS class called hiddenview and you need to define the class to hide them. While we're at it, let's also create the other views:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
    <style type="text/css" media="screen">
      .hiddenview{
        position:absolute;
        top:0;
        left:-9999px;
      }
    </style>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    function createIndex(){
      var heading = document.createElement('h1');
      viewsHandler.add('index',heading);
      viewsHandler.define('index','heading',heading);
    };
    function createDetail(){
      var heading = document.createElement('h1');
      viewsHandler.add('detail',heading);
      viewsHandler.define('detail','heading',heading);
      var para = document.createElement('p');
      viewsHandler.add('detail',para);
      viewsHandler.define('detail','intro',para);
    };
    function createInfo(){
      var heading = document.createElement('h1');
      viewsHandler.add('info',heading);
      viewsHandler.define('info','heading',heading);
    };
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
          viewsHandler.create(canvas,views);
        }
      }
    };
 }();
 myApp.init();
</script>
</body>
</html>

Showing different views

This creates all the views but doesn't show any yet. In order to change the display to a certain view, you need to call the viewsHandler.view() method with the view as a parameter.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
    <style type="text/css" media="screen">
      .hiddenview{
        position:absolute;
        top:0;
        left:-9999px;
      }
    </style>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    function createIndex(){
      var heading = document.createElement('h1');
      viewsHandler.add('index',heading);
      viewsHandler.define('index','heading',heading);
    };
    function createDetail(){
      var heading = document.createElement('h1');
      viewsHandler.add('detail',heading);
      viewsHandler.define('detail','heading',heading);
      var para = document.createElement('p');
      viewsHandler.add('detail',para);
      viewsHandler.define('detail','intro',para);
    };
    function createInfo(){
      var heading = document.createElement('h1');
      viewsHandler.add('info',heading);
      viewsHandler.define('info','heading',heading);
    };
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
          viewsHandler.create(canvas,views);
          viewsHandler.view('detail');
        }
      }
    };
 }();
 myApp.init();
</script>
</body>
</html>

This is also the way to link from one view to another, simply call the view() method. To make things easier, ViewsHandler also provides you with a method to create links showing a certain view. The viewshandler.linkto(view,label) method takes two parameters - the view to show when the link is activated and the link text. It returns an anchor element that you need to add to the correct view, normally as the last element:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
    <style type="text/css" media="screen">
      .hiddenview{
        position:absolute;
        top:0;
        left:-9999px;
      }
    </style>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    function createIndex(){
      var heading = document.createElement('h1');
      viewsHandler.add('index',heading);
      viewsHandler.define('index','heading',heading);
      var button = viewsHandler.linkto('detail','Switch to Detail view');
      viewsHandler.add('index',button);
    };
    function createDetail(){
      var heading = document.createElement('h1');
      viewsHandler.add('detail',heading);
      viewsHandler.define('detail','heading',heading);
      var para = document.createElement('p');
      viewsHandler.add('detail',para);
      viewsHandler.define('detail','intro',para);
      var button = viewsHandler.linkto('info','Switch to Info view');
      viewsHandler.add('detail',button);
    };
    function createInfo(){
      var heading = document.createElement('h1');
      viewsHandler.add('info',heading);
      viewsHandler.define('info','heading',heading);
      var button = viewsHandler.linkto('index','Switch to Index view');
      viewsHandler.add('info',button);
    };
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
          viewsHandler.create(canvas,views);
          viewsHandler.view('detail');
        }
      }
    };
 }();
 myApp.init();
</script>
</body>
</html>

Setting values of fields

The last thing to explain is how to change the value of a field once you created it. This is done with the viewshandler.set(view, label, value) method. The value could be either a string or a DOM node. If it is a string, ViewsHandler will automatically turn it into a text node. As an example, let's just set the headings and the intro paragraph of the app:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Views Demo</title>
    <style type="text/css" media="screen">
      .hiddenview{
        position:absolute;
        top:0;
        left:-9999px;
      }
    </style>
  </head>
<body> 
<div id="shell">
  <a href="http://www.flickr.com/photos/tags/rabbits">
    Rabbit photos on Flickr
  </a>
</div>
<script type="text/javascript" src="viewshandler.js"></script>
<script type="text/javascript">
  myApp = function(){
    var views = {
      index:{
        create:createIndex
      },
      detail:{
        create:createDetail
      },
      info:{
        create:createInfo
      }
    };
    function createIndex(){
      var heading = document.createElement('h1');
      viewsHandler.add('index',heading);
      viewsHandler.define('index','heading',heading);
      var button = viewsHandler.linkto('detail','Switch to Detail view');
      viewsHandler.add('index',button);
    };
    function createDetail(){
      var heading = document.createElement('h1');
      viewsHandler.add('detail',heading);
      viewsHandler.define('detail','heading',heading);
      var para = document.createElement('p');
      viewsHandler.add('detail',para);
      viewsHandler.define('detail','intro',para);
      var button = viewsHandler.linkto('info','Switch to Info view');
      viewsHandler.add('detail',button);
    };
    function createInfo(){
      var heading = document.createElement('h1');
      viewsHandler.add('info',heading);
      viewsHandler.define('info','heading',heading);
      var button = viewsHandler.linkto('index','Switch to Index view');
      viewsHandler.add('info',button);
    };
    return {
      init:function(){
        var canvas = document.getElementById('shell');
        if(canvas){
          // get data from initial shell...
          viewsHandler.create(canvas,views);
          viewsHandler.set('info','heading','This is the info heading');
          viewsHandler.set('detail','heading','This is the detail heading');
          viewsHandler.set('index','heading','This is the index heading');
          viewsHandler.set('detail','intro','This is the detail intro');
          viewsHandler.view('detail');
        }
      }
    };
 }();
 myApp.init();
</script>
</body>
</html>

This is all there is to the application demo using ViewsHandler. If you want to see more complex interaction, check the Flickr Show example.

Summary and method overview

All in all ViewsHandler is not dark magic, I just created it to make things a bit easier. This is also the reason why it is very basic and not a full templating language like other JS solutions. I wanted to keep the DOM scripting clean and still make it fast to create HTML once and only change what you need - cached for you. Hope you like it.

Following is a list of all the methods in Version 1.0:

All methods that deal with elements can either take a string which represents the ID of the element (and gets accessed via getElementById()) or a DOM node reference. This is the case when you see the parameter separated with an or "||" statement.

create(sCanvas || oDomNode, oViews)
Creates all the views defined in oViews as empty DIV elements, applies them to the canvas element and hides them (by applying a CSS class called hiddenview)
add(sView, sText || oDomNode)
adds sText to the view sView as a new child node (if you provide a string ViewsHandler automatically creates a text node)
define(sView, sField, oItem)
defines oItem (which needs to be a DOM node) as sField for sView
view(sView)
shows the view with the view name sView (by removing the hiddenView CSS class) and hides the currently shown view
linkto(sView, sLinkText)
creates a new anchor element pointing to viewshandler.view() with sView as the parameter and sLinkText as the text inside the link. Returns the anchor element - to make it appear in the view, you need to use viewshandler.add()!
set(sView, sField, sValue || oDomNode)
sets the value of sField inside the view with the name sView to sValue. If you provide a string, ViewsHandler creates a text node. If sField has child nodes, set() will replace the first child, otherwise it'll add a new child node. This avoids multiple setting.
get(sView, sField)
returns the text value of sField in sView

Creative Commons LicenseEasy Flickr - Step by Step by Christian Heilmann is licensed under a Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License. Based on a work at icant.co.uk. The software parts are licensed with the BSD license.