JavaScript for the MVC example



January 01, 2002
URL:http://www.drdobbs.com/javascript-for-the-mvc-example/184412285

WebReview.com: JavaScript for the MVC example

The DOM in Version 5 Browsers

Let's look at the JavaScript that runs our example. In studying mvctable.js, a veteran DHTML coder will find some surprises due to its use of the new DOM.

/*
 mvctable.js v0.3 Monday, May 31, 1999
 Mitch Gould - [email protected]

 by Mitch Gould, [email protected]

 This script demonstrates dynamic documents in Netscape 
Navigator and Internet Explorer 5 in the following ways: (a) it exercises W3C's standard Document Object Model API. (b) it creates, modifies, and destroys a table. (c) it partially illustrates the concept of model-view-controller. (To fully illustrate the MVC concept, modifications made to the data view would need to be propagated back to the data model.) */ // A. Data initialization. var datacount = 0 model = new Array() // Allocate the first dataset, a set of quotations, from this array. arrayquotes = new Array( "Oh dear! I shall be too late.", "Curiouser and curiouser!", "Who are -you-?", "We're all mad here.", "Twinkle, twinkle, little bat.", "Off with her head!", "I make you a present of everything I've said...", "Once, I was a real Turtle.", "Sentence first—verdict afterwards.") // When a link is clicked, the quotations' sources will be revealed // from this array. arraysources = new Array( "White Rabbit", "Alice", "Caterpillar", "Cheshire Cat", "Dormouse", "Red Queen", "Dutchess", "Mock Turtle", "Red Queen") // B. MVC-DOM methods. // Establish a new data model and assign it to a new view. function startController() { // Populate the model with initial data. datamodel = refreshModel(arrayquotes) // Get the document body element. var docbod = getBody() /* Create a view in the body of the document and fill it with the initial data. */ createView(docbod, model) } // Copy the specified dataset into the model. function refreshModel(arraycurrent) { // Populate the model with the current datastore. for(var i=0; i < arraycurrent.length; i = i + 1) { model[i] = arraycurrent[i] } return model } /* this could be generalized to provide alternatives to a a table view, such as a select object, a tree, or even a textarea. this sample produces a one-column table. Multi-column tables are more complex. */ function createView(bodyelement, themodel) { table = document.createelement("TABLE") table.border = 1 table.id = "viewtable" tablebody = document.createelement("TBODY") for(var i=0; i < model.length; i++) { currentrow = document.createelement("TR") currentcell = document.createelement("TD") currentcell.appendchild(document.createtextnode(model[i])) currentrow.appendchild(currentcell) tablebody.appendchild(currentrow) } table.appendchild(tablebody) bodyelement.appendchild(table) return table } // refresh the model first, then the view. function refreshview(dataset) { // populate the model with new data. refreshmodel(dataset) tablebody = document.getelementsbytagname("TBODY").item(0) var count = 0 replacealltext(tablebody) } // One can also destroy HTML objects using the DOM. function destroyView() { objecttodestroy = document.getelementbyid("viewtable") body = getbody() body.removechild(objecttodestroy) // now destroy the buttons. objecttodestroy = document.getelementbyid("whosaid") body.removechild(objecttodestroy) objecttodestroy = document.getelementbyid("goaway") body.removechild(objecttodestroy) } // c. dom tree-navigation and utilities. /* One must climb the trunk, branches, and twigs to get (or set!) the fruit. The recursive nature of this algorithm reflects an essential fractal nature of documents. */ function replaceAllText(startelem) { // Climb the object tree, replacing its text nodes with // new data. for (var i=0; i < startelem.childnodes.length; i = i + 1) { switch (startelem.childnodes.item(i).nodetype) { case 1: // element nodetype replacealltext(startelem.childnodes.item(i)) break; case 3: // text nodetype if (datacount < model.length) { settext(startelem.childnodes.item(i), model[datacount]) datacount = datacount + 1 } else { settext(startelem.childnodes.item(i)," - ? - ") } break; } //endswitch } //endfor } //endfunction /* many operations on dynamic documents require one to start from the document's body element. */ function getBody() { if(navigator.appName != "Netscape") { resultelement = document.body; } else { resultelement = document.getelementsbytagname("body").item(0); } return resultelement; } // utility function to overwrite text nodes. function settext(tagtoset, valuetoset) { tagtoset.nodevalue = valuetoset } // d. this can't start until the page loads. window.onload = startcontroller
Listing 2. The JavaScript source for mvctable.js.

For the sake of clarity, mvctable.js is divided into four parts:

A. data initialization,
B. MVC-DOM methods,
C. DOM tree-navigation and utilities, and
D. load-event handler.

Section A is simply expected in a script of any size. Here, it sets up the string arrays that contain the Alice quotations and their sources.

The code in the B section provides a way to implement a model-view-controller design pattern using the Document Object Model. This section acts as the controller, initializing the model and view, refreshing the model and view with updated values, and destroying the view upon request.

Section C provides support routines that encapsulate lower-level details of access to document objects. It's a bit disappointing that the DOM doesn't provide standardized access to the document at this level of encapsulation, but once these utility methods are written, they can be reused endlessly. These methods are responsible for navigating the document tree to touch the desired objects, notably in this case, text content that must be replaced.

The D section is required to start the controller as soon as the browser finishes loading the page.

With this background, we're ready to examine the code example. As the page is loading, the browser processes the data initialization section, executing the constructor

 model = new Array()

to create an empty model, and creating two more arrays: arrayquotes and arraysources.

Initializing the controller

Once the page has finished loading, the controller starts.


// This can't start until the page loads entirely.
 window.onload = startController

The controller transfers the quotes to the model, and creates a new table in the document to act as a view, as shown in Listing 3.


// B. MVC-DOM methods.

 // Establish a new data model and assign it to a new view.
 function startController() {

  // Populate the model with initial data.
  datamodel = refreshModel(arrayquotes)

  // Get the document body element.
  var docbod = getBody()

  /* 
     Create a view in the body of the document and fill it
     with the initial data. 
  */
   createView(docbod, model) 
 }

Listing 3. The startController() function.

To understand the functions called by startController(), we must turn to a description of the DOM, which we'll cover in the next section.


JavaScript for the MVC example
Understanding the Document Object Model

WebReview.com: Understanding the Document Object Model

Understanding the Document Object Model

The DOM in Version 5 Browsers

The contentious experience of the W3C's DOM task force illustrates many of the problems with designing a critical industrywide infrastructure by committee. DOM syntax went through several major changes in less than a year, to the consternation of web coders trying to author new pages according to the standard. Even the most powerful companies represented on the committee, such as Microsoft, found it hard to keep up with the various drafts. But now that their work on DOM Level 1 is done, the result can be found at the W3C site.

Frankly, the standards specification is not intended to be clear to outsiders, so some interpretation is essential. Think of a document as a fractal object, like a tree with several "nodes." In botany, a node corresponds to a root, a trunk, a branch, or a twig, terminating in a leaf or a nut. (The leaf and nut might correspond to a tag's attribute and text-data in the document model. These can't have children.) Each sort of node is an object with its own set of properties and methods.

The node is therefore the atomic unit of a document. Like a trunk or a branch, each node can have children nodes (except terminal nodes such as text-data), and each node has a parent node (except for the root node). By getting a reference to the child or parent of any node, one can recursively climb the tree and reach any part of the document.

Here's a list of nodes in XML and HTML documents.

One useful thing about a document fragment is that it allows one to move around large chunks of a document as a single object. Equally useful is the fact that unlike a document, a document fragment need not be "well formed" (that is, it need not have a perfect set of nested tags).

DHTML vs. DOM

Authors who are familiar with object models in DHTML should find the DOM reasonably easy to use—once they master the scripts required to navigate recursively through the document. For instance, consider a simplified version of Dan Steinman's cross-browser DHTML function for writing new content shown in Listing 4.

 ns4 = (document.layers)? true:false
 ie4 = (document.all)? true:false

 function simpleLayerWrite(id,text) {
  if (ns4) {
   var lyr = document.layers[id].document
   lyr.open()
   lyr.write(text)
   lyr.close()
  }
  else if (ie4) document.all[id].innerHTML = text
 }
Listing 4. Writing new content in Dan Steinman's "DynLayer" object.

This DHTML function can change the content of a div tag, given its ID value and the string for new content. Note that while the commands are straightforward, they must take very different forms to accomodate two incompatible object models. The ns4 flag is true when the browser is Navigator 4 and the ie4 flag is true when the browser is Internet Explorer 4.0. Navigator uses an object called Layer for dynamic content, while Internet Explorer addresses the div more directly.

By contrast, when using the DOM, there is only one set of code, for instance, the example shown in Listing 5.

// C. DOM tree-navigation and utilities.

 /*
   One must climb the trunk, branches, and twigs to get
   (or set!) the fruit. The recursive nature of this
   algorithm reflects an essential fractal nature of 
   documents.
 */
 function replaceAllText(startelem) {
  // Climb the object tree, replacing its text nodes with
  // new data.
  for (var i=0; i < startelem.childNodes.length; i = i + 1) {
   switch (startelem.childNodes.item(i).nodeType) {
    case 1: // Element nodetype
     replaceAllText(startelem.childNodes.item(i))
     break;
    case 3: // Text nodetype 
      if (datacount < model.length) {
       setText(startelem.childNodes.item(i), model[datacount])
       datacount = datacount + 1
      } else {
       setText(startelem.childNodes.item(i)," - ? - ")
      }
     break;
   } //endswitch
  } //endfor
 } //endfunction

 /*
    Many operations on dynamic documents require one to start
    from the document's body element.
 */
 function getBody()
  {
   if(navigator.appName != "Netscape") {
    resultElement = document.body;
   } else {
    resultElement = document.getElementsByTagName("body").item(0);
   }
   return resultElement;
 }

 // Utility function to overwrite text nodes.
 function setText(tagToSet, valueToSet) {
  tagToSet.nodeValue = valueToSet
 }
Listing 5. Replacing new content in the DOM.

Listing 5 illustrates the tree-climbing algorithm that is used when it's not feasible to locate the desired item by an ID attribute. This function accepts a branch node and recursively travels it, replacing each text node that it finds with the items in an array called "model." This is a common practice that often requires one to locate the document root—its body element, as shown in the getBody() function.

Using the DOM, an author can:

Members of the model

The DOM interface exposes objects, collections, properties, and methods. Table 1 outlines a few of the most important methods.

Selected Document Methods
createElement(tagName) 
creates a new tag.
createDocumentFragment() 
creates a new DocumentFragment.
createTextNode(data) 
creates new content for the document.
createAttribute(name) 
creates an attribute for a tag.
getElementsByTagName(tagname)  
returns a list of matching nodes (NodeList).
Selected HTMLDocument Methods
An HTMLDocument object has these methods in addition to the methods of Document.
open() 
Opens a document for new content.
close()  
Closes a document.
write(text) 
Appends the text to the open document.
writeln(text)
Appends text and a new line to the open document.
getElementById(elementId)  
returns the element with the given ID.
Selected Node Methods
insertBefore(newChild, refChild)
Inserts a child node into the document before the given node.
replaceChild(newChild, oldChild)
Replaces the old node with the new node.
removeChild(oldChild)
Removes the child node from the document.
appendChild(newChild)
Adds the node to the document.
hasChildNodes()
Returns true if the node has children.
cloneNode(deep)
Generates a copy of the tree under the given node.
Selected Element Methods
An Element has these methods in addition to the methods of Node.
getAttribute(name)
Gets the value of the named attribute.
setAttribute(name, value)
Sets the value of the named attribute.
removeAttribute(name)
Removes the named attribute.
getElementsByTagName(name)
Returns a list of matching nodes (NodeList).
Table 1. Sample list of critical DOM methods.

Creating the view

Nodes are created using the createElement and createTextNode methods. Manipulating nodes can, however, be tricky because it is very easy to build invalid tree structures, and therefore authors must familiarize themselves with the branching patterns that occur in documents. For instance, to create a well-formed table in the DOM, a TBODY element must be explicitly created and added to the tree. If the TBODY element is neglected, the nodes comprising the entire table form an invalid tree and the result is unpredictable. This example shows how to correctly add a table.

Our example's createView() function creates table-row and -cell elements and appends them as children to TBODY, as shown in Listing 6.

 function createView(bodyelement, themodel) {
  table = document.createElement("TABLE")
  table.border = 1
  table.id = "viewtable"
  tablebody = document.createElement("TBODY")
  var modelcount = 0
  for(var i=0; i < model.length; i++)
  {
   currentRow = document.createElement("TR")
   currentCell = document.createElement("TD")      
   currentCell.appendChild(document.createTextNode(model[i]))
   currentRow.appendChild(currentCell)
   tablebody.appendChild(currentRow)
  }
  table.appendChild(tablebody)
  bodyelement.appendChild(table)
  return table
 }
Listing 6. The createView() function creates a new table.

The cells are filled with the model data. Once the cells are assembled, the table body is appended to the table and the completed table is appended to the document body.

Note that elements are spawned in the document using the createElement function, but they must be actually appended to it with a separate method, appendChild.

The whole point of the MVC pattern is repurposability. Accordingly, createView could be generalized to provide alternatives to a table view, such as a select object, a tree, or even a text area. This sample produces a one-column table. Multi-column tables are more complex.

Responding to user events

When the user clicks the button, the view requests a refresh: The table needs to be updated.

The controller refreshes the model first, then the view. It copies the currently selected data into the model, and then replaces the cells' text with the contents of the model, using the utility functions in section (C), as shown in Listing 7.

 // Copy the specified dataset into the model.
 function refreshModel(arraycurrent) {

  // Populate the model with the current datastore.
  for(var i=0; i <: arraycurrent.length; i = i + 1)
   {
    model[i] = arraycurrent[i]
   }
  return model
 }

 // Refresh the model first, then the view.
 function refreshView(dataset) {
  // Populate the model with new data.
  refreshModel(dataset)

  tablebody = document.getElementsByTagName("TBODY").item(0)
  var count = 0
  replaceAllText(tablebody)
 }
Listing 7. The refreshModel() and refreshView() functions.

Is this a real MVC application?

Not really. It's a toy application, built for illustration. A real MVC application would at least have a function to edit the data that appears in the view. One point that should be stressed is that in MVC, changes to the data must be sent to the datastore first, and only then should the view be refreshed with the edited data, and not the other way around.

Dismantling the view

Some DOM methods allow one to easily manipulate nodes, notably the cloneNode(), removeNode(), replaceNode(), and swapNode() methods. These methods provide copy, move, and delete functionality to the entire document tree.

The example in Listing 8 shows how objects can be destroyed. In this function, one identifies the table and the buttons by their ID attributes, and thus it isn't necessary to navigate the document tree looking for them.

 // One can also destroy HTML objects using the DOM.
 function destroyView() {
  objecttodestroy = document.getElementById("viewtable")
  body = getBody()
  body.removeChild(objecttodestroy)
  // Now destroy the buttons.
  objecttodestroy = document.getElementById("whosaid")
  body.removeChild(objecttodestroy)
  objecttodestroy = document.getElementById("goaway")
  body.removeChild(objecttodestroy)
 }
Listing 8. The destroyView() function.

Conclusion

After a glance at DHTML incompatibilities in previous versions of the Microsoft Internet Explorer and Netscape Navigator browsers, this article explored a simple dynamic document that creates, modifies, and destroys HTML elements using the W3C's DOM API for JavaScript. This example also shows how to begin implementing the MVC design pattern in a user interface. Finally, this sample illustrates the new syntax for web pages introduced with XHTML.


Mitch Gould is a human-factors programmer for General Picture in Forest Grove, Oregon. This article is adapted from his book-in-progress, Human Factors Programming using the DOM, a guide to innovative user interfaces for the Mozilla browser. Article copyright 1999 by General Picture. All rights reserved.

JavaScript for the MVC example
Understanding the Document Object Model

WebReview.com: The DOM in Version 5 Browsers

The DOM in Version 5 Browsers

Rank: 2

W3C DOM

In the "Netscape Now!" tradition, I've created a new animated button to signify that this document complies with the W3C DOM. The gryphon was originally designed by John Tenniel, for Alice in Wonderland. Use this button free of charge on pages that adhere to the W3C's DOM standard.

Version 5 browsers from Microsoft and Netscape will support the World Wide Web Consortium's new standard Document Object Model (DOM), as Michael Floyd explained in his Feb. 1, 1999, article, "DOM and DOMer." (Netscape Navigator 5.0, also known as Gecko, is currently in beta.) This should make it easier to develop dynamic web pages that work in most browsers, without having to code for both makes of browser.

This article offers a quick view of some the incompatibilities in how the Version 4 browsers interpreted the DOM, along with a look at how the Version 5 browsers handle it differently.

We also discuss the model-view-controller design paradigm, which the DOM aspires to. MVC has become important to user-interface programming because it emphasizes a design discipline that maintains separation between content and layout.

Since some developers are understandably impatient to embark on the possibilities offered by this new architecture for web pages, we offer a simple dynamic document that shows how to change the contents of a web page through the DOM.

Finally, we offer a description of the World Wide Web Consortium's DOM standard, for those who may not be familiar with it, or who could benefit from a refresh.

Version 4 incompatibility

Incompatibilities between Netscape Navigator 4 and Microsoft's Internet Explorer 4.0 center around Microsoft's sweeping ability to make HTML elements in the document accessible to the programmer. It's no coincidence that Microsoft uses the keyword "all" to describe its collection of exposed elements. For example, to change the text content of an element with a given ID to the value strnewtext, one writes

document.all[id].innerHTML = strnewtext

Netscape Navigator 4, by contrast, is largely limited to writing new HTML into a temporary file, and then loading that document into a frame-like object called a layer. Note that the Layer tag isn't often used explicitly in Netscape DHTML documents, but its creation is implied behind the scenes, since any valid application of Cascading Style Sheets positioning to an object spawns a new layer to contain it. The corresponding Netscape code looks like this:


          var lyr = document.layers[id].document
          lyr.open()
          lyr.write(strnewtext)
          lyr.close()

To produce Version-4 cross-browser DHTML, then, one creates a "fork," a single module whose behavior is switched by the detection of either browser, like this:


        ns4 = (document.layers)? true:false
        ie4 = (document.all)? true:false

        function simpleLayerWrite(id,text) {
         if (ns4) {
          var lyr = document.layers[id].document
          lyr.open()
          lyr.write(text)
          lyr.close()
         }
         else if (ie4) document.all[id].innerHTML = text
        }

Version 5 compatibility

Now, what's different in Version 5? It's actually quite a big deal. Netscape Navigator 5.0 supports its Layer model only for backwards compatibility, and Internet Explorer 5.0 supports its "all" keyword for the same reason. Instead (at the risk of oversimplifying and sounding too optimistic), both browsers allow you to modify the document content dynamically by changing the elements of the exposed document tree using the common DOM API, as you'll see.

A cautionary note about Netscape 5.0: It's a huge undertaking that's seriously behind the original schedule. The Mozilla engineers have committed to a goal of full compliance with all W3C web publishing standards, but month after month the browser has continued to exhibit holes in essential aspects of DOM manipulation. If you try to learn the DOM by relying on the Gecko browser instead of Microsoft's (as I have) you are setting yourself up for frustrating and baffling challenges.

A dynamic document

The DOM is a Java and JavaScript application program interface (API). Using the document object model API, you can create, modify, and destroy HTML objects at will.

Here's an example. You'll need Internet Explorer 5 or Netscape's experimental Gecko browser to see it in action.

And here's the code:

<!-- [email protected] -->
<!-- Monday, May 31, 1999 v.01 -->
 <HEAD>
  <TITLE>Dynamic Table</TITLE>
   <SCRIPT SRC="mvctable.js"></SCRIPT>
 </HEAD>
 <BODY>
  <IMG SRC="w3cdom.gif" ALT="W3C DOM"> 
(Use this page only in Version-5 Netscape or Microsoft browsers.) 
A simple demonstration of the model/view/controller pattern and
the use of the W3C's standard Document Object Model. Test
(1) first, and then (2).<BR> <INPUT TYPE="button" ID="whosaid" ONCLICK= "javascript:
refreshView(arraysources);" VALUE="1. Who said?"><BR> <INPUT TYPE="button" ID="goaway" ONCLICK= "javascript:
destroyView();" value="2. Go away"><BR> </BODY> </HTML>

Listing 1. A dynamic table for Microsoft Internet Explorer 5 and Netscape Navigator 5.0.

At first glance, this HTML document looks like the pages we've been writing for several years. It has some perfunctory text and two pushbuttons.

But when the page actually loads in a Version 5 browser, we see that it also contains a table of familiar quotations from characters in Alice's Adventures in Wonderland. Since these features are created only when the page loads, and can be altered dynamically without reloading the browser, we can call this dynamic HTML (DHTML).

The buttons alter the content dynamically. Click the "Who said" button, and the quotations are replaced by the names of their sources. Click the "Go away" button, and the table is destroyed, along with the buttons themselves.

The buttons' "onclick" handlers are connected to two custom-built JavaScript methods, refreshView() and destroyView(), found in an external JavaScript file, mvctable.js.

The model-view-controller pattern

This page illustrates a kind of user interface that embodies the model-view-controller (MVC) design pattern. MVC occupies a central role in application design today.

It's an unfortunate coincidence that the word "model" appears in both "model-view-controller" and "Document Object Model"; the word means different things in each. MVC refers to a data source as the data model, and to the current contents of the display as the data view.

The relationship between these two programming disciplines (MVC and DOM) is a bit like the difference between strategy and tactics.

  • Your strategy—whether you are working in Smalltalk (where the MVC design paradigm was invented) or in Java/Swing (where MVC rose to prominence) or in JavaScript (where you push documents around)—should be to separate content from layout.
  • The tactics vary from tool to tool. In HTML, DOM is a new tactic for achieving that goal. This means to separate content from layout.
  • that just because you can do magic tricks on documents with the DOM API, it doesn't mean you have mastered the zen of MVC—and vice versa.

Since the goal of object-oriented programming is to encourage code standardization and reuse, an MVC application is factored into reusable models and views. For instance, in this case, the model consists of a standard JavaScript array and the view consists of a standard HTML table. Methods to manage a data array and to build HTML user-interface components can be made recyclable.

MVC emphasizes separation between content and presentation, thereby encouraging the same content to be displayed on devices with different capabilities. For example, on some browsers, list items may appear in an elaborate interactive list box, but very small devices might only be able to render lists as bullet text. In a world where PCs may soon no longer dominate digital communications, an MVC discipline is essential to avoiding the maintainance of multiple sets of content (read: whole web sites) tailored to each kind of user. Moreover, an MVC design discipline helps to avoid desynchronization between a local cache of data being modified by the user and the actual data source. The MVC pattern was used everywhere throughout Gecko's design.

Code that synchronizes the model with the view is called the controller, and it is not generally reusable. This scheme is shown in Figure 1.

Figure 1
Figure 1. The model-view-controller design pattern.

In this extreme example, the view object is responsible only for display, it doesn't even handle the user input, contrary to what one might expect from software components such as list boxes that handle their own selection. Rather, the controller captures user input and sends it to both the database and the view object. Regardless of the details, the real objective is to keep the tiny data window belonging to the view in sync with changes occuring to the data model.

Now let's take a look at the JavaScript that runs our example. After that, we'll offer a short look at the W3C's DOM standard.


JavaScript for the MVC example
Understanding the Document Object Model

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.