Download code

OK, so here’s my first stab at creating an MVC View in JavaScript. It doesn’t do much yet (especially since I have no Controller or Model to integrate with), but I think you’ll get the idea of where I’m going with this. Whether its the right way to go or not remains to be seen. Anyway, here it goes.

I’m creating a View class, using the Class function I worked so diligently on a couple months back. Yeah, I’m finally putting that thing to some use.

1
2
3
4
5
6
7
8
9
10
11
LivingMachines.View = LivingMachines.Class({
 
    construct: function(elem, tag)
    {
        this.container = null;
        this.initContainer(elem, tag);
    },
 
    /* More code goes here */
 
});

The construct method is, of course, the constructor for the class. It simply creates a new property named container that points to the DOM element that will contain the view. Typically, this will probably be a DIV element. Incidentally, whenever I talk about an “element” I am always referring to a DOM element. Unless I’m talking about arrays, in which case I will be referring to an array element. If I’m talking about an array of DOM elements, I will always be sure to clarify. Got it? OK.

You’ll notice that the constructor takes two arguments, elem and tag. Both are optional. The first argument is the id of an existing or non-existing element, or the actual element itself, that is to be used as a container for the View. If we are passing in the id of an element to be used as a container, but no container element with that id exists, then the tag argument tells the View what type of element to create (DIV, FORM, SPAN, etc.). If no tag argument is passed in, then it assumes a DIV element. If the element does not exist, it will be created with the given id. If no id or tag is passed in, then a unique id is generated, a DIV is automatically created with that id, and the DIV is appended to the document body. In short, we have many ways to get a container for our View.

But the constructor does none of this. It just passes the arguments on to the initContainer method, and its this method does what I just described above. The constructor just takes all the credit. You create the View and viola, you have a container element for it. At some point I may separate these. Would you ever want to construct a View, then at some point in the future get the container for it? Maybe. Possibly. As I think about it, most probably. But I’m not making that change yet. Anyway, here’s the initContainer method. The code is pretty straight-forward, I think. Just remember that elem is either an id or a DOM element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
initContainer: function(elem, tag)
{
    if ((elem != null) && (typeof elem == 'object'))  // null is considered an object. Go figure.
        this.container = elem;
    else
    {
        if (!elem) elem = 'view' + (new Date()).getTime();
        this.container = document.getElementById(elem);
        if (!this.container) 
        {
            if (!tag) tag = 'div';
            this.container = document.createElement(tag);
            this.container.setAttribute('id', elem);
            document.body.appendChild(this.container);
        }
    }
},

The code above first checks if elem is an object (line 3). If it is, then we assume the programmer knows what he or she is doing and has given us a valid DOM element, and we point the container to it (line 4). Otherwise, we assume elem is supposed to be an id. If it was not passed in, or is null, then we generate a unique id using the Date object (line 7). Next, we attempt to get the element (line 8). If we generated the id, or if the id does not have an associated element, then this will return null, so our container won’t have been set (line 9). So, we know we will need to create the container element ourselves. We check to see if a tag was passed in and, if not, set it to be a DIV (line 11). Again, we are assuming the programmer is intelligent and knows what a valid HTML tag is, so we’re not verifying the input. Then we create the container element (line 12), set the id attribute (line 13), and append it to the document body (line 14).

Now that I’m thinking about it (yes, perhaps I should have been thinking about it earlier in this process, like when I was writing the code…) there is a good chance we won’t want to append the container element to the document body, but rather to some other element. OK, that’s another one for later.

Another method of interest is render. Looks like this.

1
2
3
render: function() 
{
},

It’s not of interest because of its pithy code. No, it’s of interest because this is the method that we’ll call when we actually want to draw our View. This method must be overridden in descendant classes. And thanks to the Class function I worked so diligently on a couple months back, we can easily override it. But that’s for another post.

So, the next question then should be, what are we going to render? Well, I have a couple of methods to render a couple of things. Like forms, input boxes, labels, divs and a few others. Not a complete set yet, just a start. And no fancy data grids and stuff like that yet. Baby steps, baby steps.

Let’s look at the form method, since it’s more interesting than some of the others.

1
2
3
4
5
6
7
8
9
10
form: function(id, url, method, attribs /*, elements*/)
{
    if (!attribs) attribs = { };
 
    attribs.id = id;
    attribs.action = url;
    attribs.method = method;
 
    return this.tag('<form %a></form>', attribs, this.extractArgs(arguments, 4));
},

Yeah, I know, but I didn’t say it was interesting. I said it was more interesting than the other methods. Here’s the pattern you need to know for each of these types of element rendering methods. First, there are a series of arguments specific to the particular element we want to render. This is to make it easy to write. In this example they are id, url and method. Those arguments are followed by an optional attrib argument. This is a catch-all object of HTML attributes, given in name/value pairs. This is how we can render an element with any attributes we want. The remaining arguments are elements, and there may be any number of them. These may be actual DOM elements, or strings of HTML. These elements will be rendered inside the element we are creating. That’s why I said the form element was more interesting than the others. It has several unique arguments in the beginning, and forms always contain other elements (at least the useful ones do).

You can see that the first thing we do is simply take the first set of arguments and assign them to attributes (lines 3-7). The last line is where the money is. The tag method takes three arguments. The first is the HTML for the element that we are creating. The %a in this HTML will be replaced with the attributes. The attributes are given in the second argument as an object. The last argument is an array of DOM elements or HTML strings that are to be included inside the element that we are creating (a FORM element in this case).

OK, a brief aside about the extractArgs method. Every JavaScript function has a variable called arguments. This is an array, where each element in the array corresponds to the arguments that have been passed in to the function. So, the argument at position 0 is in arguments[0], the argument in position 1 is in arguments[1], etc. The funny thing about arguments is that it really isn’t an array at all. It just acts like one. But if you try to access any Array methods, like slice, arguments is revealed for what it is: an impostor. So, we want to get any DOM elements that have been passed in to the form method, so that we can pass them in, as an array, to the tag method. But we can’t simply call arguments.slice() since arguments has no slice method. Fortunately, we can use call to execute slice on arguments. (I talked about call, and its counterpart apply, in this post). That’s what extractArgs does. You pass it the arguments “array” and a start position, and it extracts all the arguments from the start position to the end of the arguments “array” and returns them as a real array.

1
2
3
4
5
extractArgs: function(args, start)
{
    // Convert the arguments to a real array, and extract only those elements we want.
    return Array.prototype.slice.call(args, start);
},

Now, back to that tag method. The tag method takes the HTML, the attributes, and the other elements, and returns a complete DOM element. And because it returns a DOM element, we can use these rendering methods (text, password, submit, etc.) as arguments to other rendering methods that can contain elements (form, div, span, etc). For example, here is what a View for a login might look like. Watch out, I like to use my parenthesis like curly braces!

1
2
3
4
5
6
7
8
9
10
this.div('login', null, 
    '<span>Login here:</span>',
    this.form('loginForm', 'url/to/post', 'post', { class: 'login-form' },
        this.label('Username: ', 'username'),
        this.text('username', 'Joe User'),
        this.label('Password: ', 'password'),
        this.password('password', 'Joes Password'),
        this.submit('Login')
    )
);

This is showing a couple of things. First, we are rendering a DIV (line 1). The id is ‘login’, and null is being passed in for attribs. The remaining arguments are DOM elements and/or HTML strings. That span string will be converted to a span element (line 2). Next we have the form rendering function (lines 3-9). This function returns a DOM element. Its first argument is the form id, then the url to post to, then the method to use when submitting the form. The next argument is the attribs, which is showing that we can add a class to the form attributes. The remaining arguments are DOM elements that are to be rendered inside the form element. Again, we are using our rendering functions to create those DOM elements.

So this is where it really does get interesting. Mildly, at least. This is where most of the work is done, in the tag method. Note that in this function, elements is expected to be an array (unlike in the other functions where the elements were passed in as individual arguments).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
tag: function(format, attribs, elements)
{
    // Convert the attribs object to an HTML string.
    var attribsHtml = '';
    var singleAttrib = '.compact.checked.declare.readonly.disabled.selected.defer.ismap.nohref.noshade.nowrap.multiple.noresize.';
    for (var a in attribs) {
        if (singleAttrib.indexOf('.' + a + '.') > -1) {
            if ((attribs[a] == 1) || (attribs[a] == true) || (attribs[a] == 'true') || (attribs[a] == a))
                attribsHtml += ' ' + a;
        }
        else {
            if (attribs[a]) // Make sure its not null.
                attribsHtml += ' ' + a + '="' + attribs[a] + '"';
        }
    }
 
    // Insert the attributes into the tag format
    format = format.replace('%a', attribsHtml);
 
    // Convert the HTML string to a DOM object
    var elem = this.element(format);
 
    // Add any inner element to the newly created DOM object        
    if (elements) {
 
        for (var i = 0, len = elements.length; i < len; i++) {
            if (isString(elements[i]))
                elem.appendChild(this.element(elements[i]));
            else
                elem.appendChild(elements[i]);
        }
    }
 
    // Lastly, return our new DOM element
    return elem;
},

The first thing we do is convert the attribs object into an attributes string suitable for inclusion in an HTML string (lines 3-15). I think you can figure out what the code is doing. Then, we replace the %a in the HTML format string with the attributes string we just created (line 18). Next, we convert the HTML string into a DOM object (line 21). The element method does that magic, and it’s really pretty simple. I know you’re as excited to look at it as I was writing it, but hold on for a minute, we’re not done with the tag method yet! Once we have the DOM element, we loop through the elements array and append each element there to our new element (lines 23-32). If the element is a string (checked using our isString extension function, just because we can, and we like to think we coded it for something) then it is converted to a DOM element using the element method (lines 27-28). If it is not a string, then we assume it is a DOM element and append it directly (line 30). Lastly, we return the new DOM element (line 35).

So, how do we convert those HTML strings to DOM elements? We let the browser do it for us, of course. What, you thought there was going to be some kind of hocus-pocus? I’m not that smart. Here it is, in all its glory.

1
2
3
4
5
6
element: function(html)
{
    var container = document.createElement('div');
    container.innerHTML = html;
    return container.childNodes[0];
},

First, we create a DIV element (line 1), then set the innerHTML to our HTML string (line 2). This will cause the browser to interpret the HTML and build us a DOM element. Then, we just grab the element and send it back (line 3). That’s it!

And I think that’s plenty for now. Of course, there is a unit test for this, too, but you’ll have to download the code to take a look at it.

Feedback is always appreciated!