Working out the Internet Explorer Bugs

I code on a Mac and test in Firefox during the majority of the development process. I do have a PC, sitting in the corner of my office, which I use to test code periodically to make sure everything also works in IE. (I also test Safari, Opera, Chrome and, if I’m feeling generous, Netscape) The great thing about test-driven development is that, since testing is automatic, it’s really easy to test on multiple systems. Just fire up the browser, point it to the test page, and the results are spit out for you.

But, alas, I don’t always do it. Like when I posted the code for my first stab at creating the MVC View. Turns out, there was a problem running the code on Internet Explorer.

The problem was with generating the unique id for the View when one was not given in the constructor. The initContainer function would generate a unique id for you using the getTime function, like so:

1
2
if (!elem) 
    elem = 'view' + (new Date()).getTime();

If elem was not passed in, or was passed in as null, then this would create a new date object and get the time. new Date() should return a new date object with the current date and time, since we are not passing in any arguments. The getTime function returns the number of milliseconds since January 1, 1970. Theoretically, then, each call to getTime should return a new number, since we will not be creating Views within one millisecond of each other. And this works just fine on all browsers except Internet Explorer. Seems IE always returns the same value for getTime. That is unless you get the value, then display it in an alert box. In that case it does function properly. But that’s not a very user-friendly solution.

So, I came up with a solution that uses a static class variable. After creating the LivingMachines.View class, I added a static variable called nextId (line 5).

1
2
3
4
5
LivingMachines.View = LivingMachines.Class({
    /* Lots of code excluded for brevity */
});
 
LivingMachines.View.nextId = 0;

I then use this static variable to create the unique id’s for my Views in initContainer.

1
2
if (!elem) 
    elem = 'view' + LivingMachines.View.nextId++;

This creates the View id by using the value of nextId, then incrementing that value by 1 so that it’s ready for the next View to be created (hence the name nextId and not currentId).

So, there you have it, static class variables and a fix for IE. And now I know to avoid getTime in IE.

I am currently working on expanding the View’s functionality, and now I have to go fix another IE problem that has cropped up with some of the new code that I am writing. Actually, since it’s after 1 am here, I probably need to go to bed.

Comments (2)

First Stab at Putting the V in MVC

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!

Leave a Comment

Where to Begin in the MVC Triangle?

Now that the JavaScript extensions are released, I have spent some time debating on where to begin with the main core of the library. That is, the MVC portion. I want to make sure that I have time to contribute to this blog on a regular basis (something I am struggling with already) while at the same time not losing focus on my other projects. Hence, I need to find a balance.

My decisions of what to blog and what code to share will be based on the needs of my current project. Hopefully, this project will proceed in a logical, orderly manner (ha, ha, ha), and so the JavaScript library can be built at the same time and also in a logical, orderly manner (getting that dreamy look in my eyes just thinking about it). More importantly, and perhaps more difficult, I must resist the urge to use already existing libraries which might make my project go faster.

Of course, in this I have already failed. I am already using the YUI Testing Tools. But, at least that’s only on the developer side, and not the end-user side.

Anyway, I finally decided that since the View is where it all starts (user input) and stops (system output) in an MVC architecture, that it would be a good place to begin. Also, I typically develop with the user interface first, since the UI really describes how the application should function, so it makes sense to begin with the View. With all that said, I do want to point out that my first and foremost priority is to get the “plumbing” in place. Therefore, I’m not going to spend a whole lot of time on fancy animations and other visual icing. And I’m sure I’ll be delving into Controllers and Models fairly quickly, since a View without them won’t really do anything useful.

What is this MVC of which you speak?

So, how about an MVC review before we get started, eh? MVC stands for Model-View-Controller, and represents the holy triad of application design.

The Model is the representation of the real-world data that makes up the application. The model is typically a database model, but could be any internal representation of real data.

The View is the visual presentation of the Model, and its the only portion of the MVC architecture that the user will interact with. Typically, the view is associated with the computer screen, but it could be just a small section of the computer screen or some other representation of the data, such as a printout.

The Controller is responsible for application state. It handles the communication between the View and the Model. When a user makes a gesture on a View to save data, by clicking on a “Save” button for example, the Controller passes that request and the data that was submitted to the Model. The Model then saves the data and passes the results back.

The MVC rules of engagement

That’s the MVC in a nutshell.  But to be more specific, here are the rules I will follow when implementing MVC in this library:

Model Rules

  1. Can read and write data directly to the underlying store (i.e. the server).
  2. No interaction with the user.
  3. Does not directly change the application state, but can change application data.
  4. Does not listen to any events (it is told what to do, then it fires events to report results).

View Rules

  1. No direct access to underlying data store.  Can only read data through a Model (cannot request the Model to update any data).
  2. Heavy interaction with the user (user gestures are fired as events to Controllers).
  3. Does not change the application state (but can change its own state, for example to show visual cues to the user).
  4. Listens to events from Models (application data changes) and Controllers (application state changes).

Controller Rules

  1. No direct access to the underlying data store.  Can read and write data through requests to a Model.
  2. No interaction with the user.
  3. Controls and directs all application state changes (makes requests to the Model to change application data).
  4. Listens to events from Models (application data changes), Views (user gestures) and other Controllers (application state changes).

Here is the short version of those same rules:

  1. A Model does NOT alter the state of the application, and does NOT interact directly with the user or format data for display.
  2. A View does NOT change application data and does NOT change the state of the application.
  3. A Controller does NOT change application data, and does NOT interact directly with the user or format data for display.

So there you have it, the basic rules for the library. I’ll try to post code often, even if it is incomplete, so you can see not only what I’m doing, but also why I’m doing it. Look for the first installment next week (I recommend subscribing to the RSS feed)!

Leave a Comment

Even More JavaScript Extensions: Dates

Download code

Continuing with JavaScript extensions, I’ve created a set of extensions to the Date data type. These include some functions for date calculations, comparisons and formatting. I’ve also added a new function to the String data type that will convert a string into a Date object.

Extensions to the Date object

Let’s start with the simpler one first, the Date object. Most of the functions are pretty self-explanatory, but there are some notable things I should point out.

First, there are the static arrays that have been added to the Date data type.

1
2
3
4
5
6
7
Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
Date.shortDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
Date.dayChars = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
 
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
Date.shortMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date.monthChars = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'];

Lines 1 to 3 contain the names of the days of the week, and lines 5 to 7 contain the names of the months. You can override these to provide localized names and abbreviations.

These are used to convert a weekday number or month number to a string version. So, for example, I could use Date.monthNames[3] to get the value “April”. But sometimes I need to be able to go the other way as well, like when converting a date string to a Date object. The next block of code sets up for this type of conversion.

1
2
3
4
5
6
Date.monthNumbers = { };
Date.shortMonthNumbers = { };
for (var i = 0; i < 12; i++) {
  Date.monthNumbers[Date.monthNames[i]] = i;
  Date.shortMonthNumbers[Date.shortMonthNames[i]] = i;
}

The above code creates two objects, one for long month names and one for short month names. I saw no need to convert week day names to numbers, so those are not included here. Once the empty objects are set up, it loops through the month names, adding each one as a property to the empty month numbers objects, and setting its value to the corresponding month number. This allows me to look up a month number from the name by simply using Date.monthNumbers['January']. Remember that month numbers in JavaScript are zero-based, so January is 0, not 1. I’m doing this to make it easier and faster to convert month names to numbers. I’m using the loop to build the objects so that I only have to set the month names one time (in the arrays I created earlier). This loop uses those values, saving me from having to re-enter them. This means I can localize the date strings in one place.

Another function of note that I added is the copy function. When you assign one date variable to another, JavaScript creates a pointer to that date object. This means that both variables will point to the same date object, which is not always desirable. The copy function allows you to quickly create a copy of a date.

1
2
3
var d1 = new Date(2009, 03, 20);  // April 20, 2009
var d2 = d1;  // Creates pointer to d1
var d3 = d1.copy();  // Creates a copy of d1

The last function of interest is the toFormat function. This allows you to easily convert a Date object to a string representation.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
Date.prototype.toFormat = function(format) 
{
    var pad = function(val) { 
        return (val > 9 ? '' : '0')+val; 
    };
    var result = '';
    for(var i = 0, len = format.length; i < len; i++) {
        var c = format.charAt(i);
        switch(c) {
            case 'Y': 
                result += this.getFullYear();
                break;
            case 'y': 
                result += (this.getFullYear()+'').substr(2,2);
                break;
            case 'M': 
                result += pad(this.getMonth()+1);
                break;
            case 'm': 
                result += this.getMonth()+1;
                break;
            case 'N':
                result += Date.monthNames[this.getMonth()];
                break;
            case 'n': 
                result += Date.shortMonthNames[this.getMonth()];
                break;
            case 'D': 
                result += pad(this.getDate());
                break;
            case 'd': 
                result += this.getDate();
                break;
            case 'W': 
                result += Date.dayNames[this.getDay()];
                break;
            case 'w': 
                result += Date.shortDayNames[this.getDay()];
                break;
            case 'H': 
                var hour = this.getHours() % 12;
                result += pad((hour ? hour : 12));
                break;
            case 'h': 
                var hour = this.getHours() % 12;
                result += (hour ? hour : 12);
                break;
            case 'R': 
                result += pad(this.getHours());
                break;
            case 'r': 
                result += this.getHours();
                break;
            case 'I': 
                result += pad(this.getMinutes());
                break;
            case 'i': 
                result += this.getMinutes().toString();
                break;
            case 'S': 
                result += pad(this.getSeconds());
                break;
            case 's':
                result += this.getSeconds().toString();
                break;
            case 'A': 
                result += (this.getHours() < 12 ? 'AM' : 'PM');
                break;
            case 'a': 
                result += (this.getHours() < 12 ? 'am' : 'pm');
                break;
            default:
                result += (c == '^' ? format.charAt(++i) : c);
        }
    }
    return result;
};

The function takes a single format string and replaces certain characters within that string with the actual date values. Lines 3 to 5 are a small function used to left pad a single digit with a zero. I’m using this version rather than the String.leftPad method (part of the string extensions) simply for performance reasons. This little pad function assumes that the source value is a number and uses type coercion to convert the value, padded or not, to a string. It will also only pad with a single digit. These assumptions make it faster than the String.leftPad method.

Next, the code simply loops through each character of the format string, looking for those special characters that it needs to replace with the date values. Those special characters are given in the following table:

Field Long form Short Form
Year Y (4 digit) y (2 digit)
Month M (2 digit) m (1 or 2 digit)
Month Name N (full name) n (abbreviation)
Day of Month D (2 digit) d (1 or 2 digit)
Day Name W (full name) w (abbreviation)
Hour (1-12) H (2 digit) h (1 or 2 digit)
Hour (0-23) R (2 digit) r (1 or 2 digit)
Minute I (2 digit) i (1 or 2 digit)
Second S (2 digit) s (1 or 2 digit)
AM/PM A (upper case) y (lower case)
literal ^ (insert next character, do not replace)


So, for example, you can use the following formats:

1
2
3
4
5
6
7
8
9
10
11
12
var birthDate = new Date(1980, 0, 1);  // Januatry 1, 1980
 
// Alerts: January 1, 1980
alert(birthDate.toFormat('N, d, Y'));
 
// Alerts: 01/01/80
alert(birthDate.toFormat('M/D/y'));
 
// Alerts: Birthday is Jan 01 '80
alert(birthDate.toFormat("B^i^rt^h^d^a^y is n, D 'y"));
// This is probably a better way to do the above:
alert(String.toFormat('Birthday is %s', birthDate.toFormat("n, D 'y"));

Note that I decided to use the caret ^ for literals rather than the more typical backslash. This is because JavaScript already uses the backslash as an escape character. So, to insert a backslash into a JavaScript string you would always need to insert two backslashes, which I felt would be annoying, not very readable, and easy to forget. For example, you would have to enter a string such as ‘\\D\\ate: M/D/Y’. Each double backslash is interpreted by JavaScript as a single backslash. If you entered it as ‘\D\ate: M/D/Y’, then JavaScript would try to escape the first ‘D’ and the ‘a’.

The other included Date object extension functions are pretty self-explanatory, and the code should be fairly easy to interpret.

More Extensions to the String Object

I’ve also added a new function to the String object, toDate. This method will read the value of the string and convert it into a Date object. As you might imagine, this is a bit more complicated than converting a Date to a String. The basic premise is to use regular expressions to extract the parts of the string that represent the various date part that we are looking for. Once we have all the parts, we convert them as necessary into numeric values that we can use to create a Date object.

The first part of the function sets up a number of variables. I’ll discuss the Pos variables (yearPos, monthPos, dayPos, etc.) in a moment. Just note that they are all set to -1.

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
    // Default values set to midnight Jan 1 of the current year.
    var year = new Date().getFullYear();
    var month = 0;
    var day = 1;
    var hours = 0;
    var minutes = 0;
    var seconds = 0;
 
    // Positions of each date element within the source string.  Use to know 
    // which backreference to check after a successful match.
    var yearPos = -1;
    var monthPos = -1;
    var dayPos = -1;
    var hoursPos = -1;
    var minutesPos = -1;
    var secondsPos = -1;
    var amPmPos = -1;
 
    var monthStyle = 'm';       // How we interpret the month, digits (M/m) or names (N/n)
    var hoursStyle = 'h';       // How we interpret the hours, 12-hour (h) or 24-hour (r)
 
    var position = 1;           // Position of the current date element (year, month, day, etc.) in the source string
    var pattern = '';           // Date pattern to be matched.
 
    // Remove extraneous whitespace from source string and format string.
    var str = this.replace(/\s+/g, ' ');
    format = format.replace(/\s+/g, ' ');

The Pos variables in lines 11 to 17 will be set to the position within the source string where a particular value is found. This will be used to retrieve the value from the matches array that is returned when the regular expression is executed. The last thing this block of code does is remove any extraneous whitespace from the source and format strings. I thought it would be safe to do that, and probably helpful when interpreting input from a user which may not be as clean as I would like.

The next block of code, shown below, loops through the format string and builds the regular expression string that will be used to extract the date values we need. This is where we set the value of the Pos variables (yearPos, monthPos, dayPos, etc.). Note that for ‘W’ and ‘w’ in the format string (weekday names) we will match them in the regular expression, just to ensure that the string is the correct format, but we don’t actually need them to create a Date object, so we will ignore their matched values. The format string uses the same notation as the Date.toFormat function discussed above.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    // Loop throught the format string, and build the regex pattern
    // for extracting the date elements.
    for (var i = 0, len = format.length; i < len; i++) {
        var c = format.charAt(i)
        switch (c) {
            case 'Y' :
                pattern += '(\\d{4})';
                yearPos = position++;
                break;
            case 'y' :
                pattern += '(\\d{2})';
                yearPos = position++;
                break;
            case 'M' :
            case 'm' :
                pattern += '(\\d{1,2})';
                monthPos = position++;
                monthStyle = 'm'
                break;
            case 'N' :
                pattern += '(' + Date.monthNames.join('|') + ')';
                monthPos = position++;
                monthStyle = 'N';
                break;
            case 'n' :
                pattern += '(' + Date.shortMonthNames.join('|') + ')';
                monthPos = position++;
                monthStyle = 'n';
                break;
            case 'D' :
            case 'd' :
                pattern += '(\\d{1,2})';
                dayPos = position++;
                break;
            case 'W' : // We'll match W, but won't do anything with it.
                pattern += '(' + Date.dayNames.join('|') + ')';
                position++;
                break;
            case 'w' : // We'll match w, but won't do anything with it.
                pattern += '(' + Date.shortDayNames.join('|') + ')';
                position++;
                break;
            case 'H' :
            case 'h' :
                pattern += '(\\d{1,2})';
                hoursPos = position++;
                hoursStyle = 'h';
                break;
            case 'R' :
            case 'r' :
                pattern += '(\\d{1,2})';
                hoursPos = position++;
                hoursStyle = 'r';
                break;
            case 'I' :
            case 'i' :
                pattern += '(\\d{1,2})';
                minutesPos = position++;
                break;
            case 'S' :
            case 's' :
                pattern += '(\\d{1,2})';
                secondsPos = position++;
                break;
            case 'A' :
            case 'a' :
                pattern += '(AM|am|PM|pm)';
                amPmPos = position++;
                break;
            default :
                pattern += (c == '^' ? format.charAt(++i) : c);
        }
    }

At the end of all this, the pattern variable will contain our regular expression. We can then use that pattern to check the source string for matches. That’s what the next section of code does, below. If no match is found, we return null, meaning that the source string was not in the format we expected.

1
2
3
4
    // Pull out the date elements from the input string
    var matches = str.match(new RegExp(pattern));
    if (!matches)
        return null;

Next we have to interpret all of those parts. If one of the Pos variables has a value greater than -1, then we know that there should be a match for that field. The value will be stored in the matches array that was returned from the regular expression engine. The position of the value within that array is the value of the Pos variable. In other words, if yearPos > -1, then we can find the value of the year field at matches[yearPos].

Now, because we’ve used regular expressions, we know that if we were looking for a number, then we’ve got a number, and we won’t need to worry that we can’t convert it to an actual Number data type. The monthStyle is used to record whether we are looking for a month number or name. That’s the only value that could be returned as both a number or a name.

Where possible, we will also check the ranges of the values, to make certain that the date entered is valid.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    // Now we have to interpret each of those parts...
 
    if (yearPos > -1) {
        year = parseInt(matches[yearPos], 10);
        year = (year < 50 ? year + 2000 : (year < 100 ? year + 1900 : year));
    }
 
    if (monthPos > -1) {
        switch (monthStyle) {
            case 'm':
                month = parseInt(matches[monthPos], 10) - 1;    // JavaScript months are zero based, user input generally is not.
                if (month > 11)
                    return null;
                break;
            case 'N': 
                month = parseInt(Date.monthNumbers[matches[monthPos]], 10);
                if (isNaN(month))
                    return null;
                break;
            case 'n':
                month = parseInt(Date.shortMonthNumbers[matches[monthPos]], 10);
                if (isNaN(month))
                    return null;
                break;
        }
    }
 
    if (dayPos > -1) {
        day = parseInt(matches[dayPos], 10);
        if ((day < 1) || (day > Date.daysInMonth(month, year)))
            return null;
    }
 
    if (hoursPos > -1) {
        hours = parseInt(matches[hoursPos], 10);
        if (hoursStyle == 'h' && (hours == 0 || hours > 12))
            return null;
        else if (hours > 23)
            return null;
    }
 
    if (minutesPos > -1) {
        minutes = parseInt(matches[minutesPos], 10);
        if (minutes > 59)
            return null;
    }
 
    if (secondsPos > -1) {
        seconds = parseInt(matches[secondsPos], 10);
        if (seconds > 59)
            return null;
    }
 
    // Convert 12-hour time, if used, to 24-hour time.
    if (amPmPos > -1) {
        var amPm = matches[amPmPos];
        if ((amPm == 'pm' || amPm == 'PM') && (hours < 12))
            hours += 12;
    }

An enhancement to this code would be to provide a specific error code and message for a date conversion failure. For example, month is out of range, or February does not have a 29th day in 1999. But that’s for another time.

The last thing to do is take all the values we’ve extracted from the string, and convert them to an actual Date object.

1
    return new Date(year, month, day, hours, minutes, seconds);

Of course, tests are included for all extension functions as well.

Date Extensions

Date.daysInMonth(month, year)
Returns the number of days in the current month and year. Note that the month is zero-based, so January = 0, February = 1, etc.

Date.prototype.copy()
Creates a copy of the current date object. Assigning one date variable to another simply points both variables to the same date object. This function is useful when you need a unique copy of the date object.

Date.prototype.getDayName()
Returns the full name of the day of the week.

Date.prototype.getShortDayName()
Returns the short name of the day of the week, typically in 3 characters.

Date.prototype.getDayChar()
Returns the single character representation of the day of the week.

Date.prototype.getMonthName()
Returns the full name of the month.

Date.prototype.getShortMonthName()
Returns the short name of the month, typically 3 characters.

Date.prototype.getMonthChar()
Returns the single character representation of the month.

Date.prototype.getMonthNumber()
Returns the normalized numeric representation of the month. (i.e. January = 1, February = 2, …, December = 12)

Date.prototype.daysInMonth()
Returns the number of days in the current month and year, adjusting February for leap years.

Date.prototype.addDays(offset)
Adds the given number of days to the date. To subtract days, pass in a negative value for offset.

Date.prototype.addMonths(offset)
Adds the given number of months to the date. To subtract months, pass in a negative value for offset.

Date.prototype.addYears(offset)
Adds the given number of years to the date. To subtract years, pass in a negative value for offset.

Date.prototype.addHours(offset)
Adds the given number of hours to the time portion of a date. To subtract time, pass in a negative value for offset.

Date.prototype.addMinutes(offset)
Adds the given number of minutes to the time portion of a date. To subtract time, pass in a negative value for offset.

Date.prototype.addSeconds(offset)
Adds the given number of seconds to the time portion of a date. To subtract time, pass in a negative value for offset.

Date.prototype.clearTime()
Clear the time portion of a date object.

Date.prototype.compareTo(date, ignoreTime)
Date comparison functions. If ignoreTime is true, then the time portion will be ignored during the comparison.

Date.prototype.isBefore(date, ignoreTime)
Returns true if this date is before another date.

Date.prototype.isAfter(date, ignoreTime)
Returns true if this date is after another date.

Date.prototype.equals(date, ignoreTime)
Returns true if this date is equal to another date.

Date.prototype.toFormat(format)
Return a date in the given format. Use ^ to force the use of a literal character.

String Extensions (showing new only)

String.prototype.toDate(format)
Attempts to convert a string into a date based on a given format. Fields will match either the long or short form, except in the case of the year, where the string must match either a 2-digit or 4-digit format. Ranges are checked. Day names are expected if they are included in the format string, but are otherwise ignored. Use ^ to force the use of a literal character. In other words, to have the character Y appear insead of the actual year, use ^Y.

Tags:

Comments (2)

More JavaScript Extensions, and a Library Framework

Download code

As I mentioned in my previous post, I’m in the process of creating some JavaScript functions that extend the basic JavaScript data types. Well, I’ve create a few extensions for the String and Number data types. Also, since I’m building a JavaScript library, I went ahead and packaged the source code files into a library as well.

JavaScript Extensions

How do you extend the basic data types you ask? There are two ways. You can either create a static method, which is available from the from the data type itself, or a prototype method, which is available from all instances of that data type.

For example, the repeat function is a static method of the built-in JavaScript String class. It was created like this:

1
2
3
4
5
6
7
String.repeat = function(chr, count)
{
  var s = '';     
  for(var i = 0; i < count; i++) 
    s += chr;     
  return s;
}

You can then execute the repeat method like so:

1
2
var str = String.repeat('x', 5);
alert(str);  // Alerts: "xxxxx"

On the other hand, the rightPad function was created as a prototype method of the String class. If you recall from the Creating JavaScript Classes series, prototype methods are accessible from all object instances. This means any instance of a String will have this method.

1
2
3
4
5
6
7
8
9
10
String.prototype.rightPad = function(width, chr, trunc)
{
  var s = this.toString();
  var w = width - s.length;
  if (w > 0)
    s = s + String.repeat(chr, w);
  else if (trunc)
    s = s.substr(0, width);
  return s;
}

Now, there are two things to note in the above. First, the declaration uses String.prototype.rightPad to add the rightPad function to the prototype of the String class. Second, because the rightPad method is now available to a String object instance, the this variable is available in the function, and it will point to the current String instance.

Also of particular interest is line 3, which uses this.toString() to create a copy of the current string. It is this copy that is modified and returned by the function. This is common functionality in JavaScript, and I wanted to follow that same pattern here. For example, the replace method does not alter the string, but returns a new string with the replacements made.

The String extensions include basic string manipulation function like padding, trimming and formatting. I’ve also include a function to convert a string to a number, while considering how a user might enter a number. So these values could include thousands separators, currency symbols, or even fractions. I often find that users appreciate the ability to enter fractions rather than decimals. Its easier for a user to enter 1/8 rather than remembering or calculating the decimal value 0.125. And I’m a big believer that the user should not be calculating anything. That’s what the computer is for!

The Number extensions include a toFormat function that will take the number and convert it to a string. Again, this can take a number like 0.125 and convert it back to the fraction 1/8, if desired. I could have overridden the built-in toString function, but it has its own purpose that developers may still want to use, so I left it alone.

Here are some examples of using these extensions. To format a number, you can use:

1
2
3
var num = 1234.56;
var str = num.toFormat(3, true);
alert (str);  // Alerts: "1,234.560"

Or, if you want a fraction, set the first argument (the precision) to -1, like so:

1
2
3
var num = 1.333;
var str = num.toFormat(-1);
alert (str);  // Alerts: "1-1/3"

Using the string extensions, you can parse the fraction:

1
2
3
var str = '1-1/3';
var num = str.toNumber();
alert (num);  // Alerts: "1.3333"

To pad a string, use something like this:

1
2
3
var str = '123';
var pstr = str.leftPad('0', 5);
alert (pstr);  // Alerts: "00123"

Of course, you can chain all these functions, too:

1
2
3
var num = 123.4;
var str = num.toFormat(2).leftPad('*', 8);
alert (pstr);  // Alerts: "**123.40"

This is just a sample of the extensions that are included. All of them are pretty straight-forward, and the code is well-documented (in my opinion, let me know if you feel otherwise). And, of course, all of the unit tests are in there as well. Please keep in mind that these are first versions, so don’t expect perfection yet! I can already see where I’d like to make some additions and changes to these functions. I’m still in the early stages here, so I’ll try to keep things backward compatible. But that may not be possible. You’ve been warned!

JavaScript Library Framework

The other thing I did in this version is to package these extensions along with the Class function from the Creating JavaScript Classes series, into a actual library. I also added a namespace, and, come to think of it, I should probably also add a namespace function. Will add that to my list of things to do. Anyway, to use the Class function, you will now access it using LivingMachines.Class(). Otherwise, it’s usage is the same as previously described in the Creating JavaScript Classes series. Going forward, all additional code will be added to this new library package.

Well, I hope you find these useful and educational. This is only the start of the library, so make sure to subscribe to the RSS feed so that you can keep up to date on new developments. Next up, I’m going to work on some date formatting functions. This means I’ll be adding some functions to the Data data type, and to the String data type as well.

Included JavaScript Extension Functions

Here is a quick overview of the new JavaScript extensions included in this release. A quick look at the source code comments will give you more details on function usage. Eventually, I will get around to converting the documentation comments to online docs. I’ve got to investigate some of the tools that are out there for doing that, though.

String Extensions

String.repeat()
Repeats a character or string a given number of times.

String.prototype.trim()
Trims whitespace from the left and right sides of a string.

String.prototype.leftTrim()
Trims whitespace only from the left side of a string.

String.prototype.rightTrim()
Trims whitespace only from the right side of a string.

String.prototype.leftPad()
Pads the left side of a string with a given character to a given width.

String.prototype.rightPad()
Pads the right side of a string with a given character to a given width.

String.prototype.toFormat()
Takes a source string with value placeholders and replaces the placeholders with actual values.

String.prototype.toNumber()
Converts the string to a number.

Number Extensions

Number.prototype.toFormat()
Converts a number to a formatted string.

Number.prototype.gcd()
Calculates the greatest common divisor between this number and another number.

Tags:

Leave a Comment