Friday, October 24, 2008

Cross-Browser JavaScript: Creating DOM Nodes That Set The name Property

Creating and appending DOM nodes to a form can be tricky. Internet Explorer needs special care when you create a DOM node and then set the name property. This handy JavaScript gets around these cross browser issues efficiently, and without browser detection.

Web browsers have come a long way. We can create Document Object Model nodes at will using the universally supported document.createElement function, then you try creating form fields in Internet Explorer and you hit a snag.

Form fields, in order to be useful, need a name attribute in HTML. In JavaScript, you set the name property. It's two simple lines of code. Nothing special. No hoops to jump through:

// Create a new form input to hold a first name:
var el = document.createElement("input");
el.name = "fname";

You append this new input to your form and then try to access it using the form.elements.field_name syntax and you can't find the "fname" field. Turns out, Internet Explorer requires a little fudging:

// Create a new form input for Internet Explorer:
var el = document.createElement("<input name=fname>");

In one fell swoop, you must create the new element AND assign the name property before Internet Explorer will append this element to the form's document object model, making it available through the form.elements.field_name syntax. The problem is, standards compliant browsers, i.e. "every other browser developed within the last 8 years," will throw a JavaScript error if they try executing that code.

You could try browser detection, which usually involves parsing the user agent string of the browser. Most browsers allow you to change the user agent string, so this is no longer a viable method. Plus, what do you do when a new browser or browser version comes out? You'd have to change your function. Turns out, a simple try-catch block is the answer.

Creating a named DOM node the coss browser way

We can create our own createElement function to account for the difference in Internet Explorer's implementation of document.createElement:

function createElement(nodeName, name) {
var node;
try {
node = document.createElement("<"+nodeName+" name="+name+">");
} catch (e) {
node = document.createElement(nodeName);
node.name = name;
}
return node;
}

It works fine and dandy. Every time you call the function, it tries doing it Internet Explorer's way, but you take a performance hit because of the try-catch block that executes every time the createElement function executes. With a little runtime retooling, we can get around this using three functions instead of one:

/**
* Cross browser method for creating new DOM nodes with name attributes. This
* gets around an Internet Explorer bug that prevents that get the name property
* assigned to them and their values aren't sent when the form submits.
*
* @param string required Node name, like "div" or "input"
* @param string required Value of the name attribute
* @return object New DOM node
*/
function createElement(nodeName, name) {
var node;
try {
node = createElementMsie(nodeName, name);
createElement = createElementMsie;
} catch (e) {
node = createElementStandard(nodeName, name);
createElement = createElementStandard;
}
return node;
}

/**
* Code required by Internet Explorer when creating a new DOM node with the
* name attribute set.
*/
function createElementMsie(nodeName, name) {
return document.createElement("<"+nodeName+" name="+name+">");
}

/**
* Code required by all other browsers that support web standards.
*/
function createElementStandard(nodeName, name) {
var node = document.createElement(nodeName);
node.name = name;
return node;
}

When the page first loads, you've got three functions. The createElement function, which is what you will always use, has a try-catch block that calls two other functions. The createElementMsie function attempts to create the new DOM node using Internet Explorer's implementation. If the function call succeeds, the createElement function is reassigned to be the createElementMsie function. All subsequent calls to createElement will run the code in createElementMsie.

In the catch clause, the browser has failed to execute createElementMsie, and therefore doesn't support Internet Explorer's implementation of document.createElement. Here we assume it is a standards compliant browser and doesn't need any hand holding. It calls createElementStandard, and when that succeeds it reassigns the createElement function to be the createElementStandard function. All subsequent calls to createElement run the code in createElementStandard.

You still take a performance hit with these three functions because the try-catch block is executed, but only for the first time you call createElement. From the second time you call createElement onwards, it directly executes the code the browser supports. The try-catch block doesn't exist anymore, and you've gotten around Internet Explorer's faulty implementation of the document.createElement function. You can now create form fields at will and feel safe in knowing Internet Explorer will actually send those field values to the server when the form is submitted.

Using createElement:
var el = createElement("input", "fname");
// append the new INPUT to the form.

Tuesday, October 7, 2008

JavaScript Zebra Stripes: Alternate Background Colors on Tables and Lists

Love it or hate it, alternating background colors is a common design request for web designers. If you maintain a site of static HTML files, this "zebra striping" effect can become time consuming and tedious. Even if you use a content management system or a server side language, automation is your friend. This script is the automation you want.

There are tons of similar scripts on the Internet, and all do a great job. I've found they have some short comings. I don't always just want to alternate background colors on table rows. I often do not want cells in the table header and footer to have alternating colors either. Things get real messy when I want alternating colors on list items, and especially definition lists. This script package seeks to solve all these problems.

Browser Support

All standards compliant browsers are supported, as well as Internet Explorer version 5.0 and newer. If the browser supports the *.getElementsByTagName method, then the browser supports this script. This is also safe to use with any pre existing JavaScript library or framework. It packages all the related functions into an object called stripes. As long as you don't have a global variable or function named "stripes," this is safe to use, and since it utilizes only standard DOM API calls, no additional JavaScript libraries are required.

Using the stripes functions

Usually you'll want some sort of JavaScript event to kick things off. I recommend the onload event if you don't use a JavaScript framework like Mootools or jQuery. A simple function call in the onload attribute of the body tag will suffice:

<body onload="stripes.execute();">

For those who like unobtrusive JavaScript, you can add this to the window.onload function:

window.onload = function() {
stripes.execute();
};

The stripes.execute() function will parse the entire document object model. For very large HTML files, you might not want the function parsing the entire DOM tree. You can specify a root DOM element from which to start searching by passing an HTML tag Id or DOM node reference to the stripes.execute() function. If no argument is passed, or the Id passed does not exist in the HTML document, then the document element is used.

// Only search in <div id="content">

window.onload = function() {
stripes.execute( "content" );
};

/* --- OR --- */

window.onload = function() {
var contentNode = document.getElementById( "content" );
stripes.execute( contentNode );
};

How it works

The only thing this script package does is change class names on DOM node elements. For tables, a class name is assigned to the <tr> tag. For unordered and ordered lists, the class name is assigned to the <li> tag. Lastly, for definition lists the class name is assigned to the <dt> and <dd> tags. All you need to do is add style rules to your CSS.

The script comes preprogrammed with two class names. Even rows or list items are given the class "rowA". Odd rows or list items are given the class "rowB". You can easily change the class names in the JavaScript source code. Below is a snippet showing you where you can change these default class names:

window.stripes = {

stripeRowEven : "rowA",
stripeRowOdd : "rowB",
stripeParentClass : "stripes",


...

};

The stripeRowEven and stripeRowOdd properties contain the class names assigned to table rows, list items, and definition terms and descriptions. The third property, stripeParentClass, is the class name the script will search for when making the zebra stripes. As mentioned earlier, this script works for tables, unordered lists, ordered lists and definition lists:

HTML Structure Supported for TABLEs
<table cellpadding="5" cellspacing="0" border="1" class="stripes">
<thead>
<tr>
<th>Heading 1</th>
<th>Heading 2</th>
<th>Heading 3</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Footer 1</th>
<th>Footer 2</th>
<th>Footer 3</th>
</tr>
</tfoot>
<tbody>
<tr>
<td>Cell 1.1</td>
<td>Cell 1.2</td>
<td>Cell 1.3</td>
</tr>
<tr>
<td>Cell 2.1</td>
<td>Cell 2.2</td>
<td>Cell 2.3</td>
</tr>
</tbody>
</table>
HTML Structure for Lists
<ul class="stripes">
<li>List Item 1</li>
<li>List Item 2</li>
</ul>

<ol class="stripes">
<li>List Item 1</li>
<li>List Item 2</li>
</ol>

<dl class="stripes">

<dt>Definition Term 1</dt>
<dd>Description 1.1</dd>
<dd>Description 1.2</dd>

<dt>Definition Term 2</dt>
<dd>Description 2.1</dd>
<dd>Description 2.2</dd>
<dd>Description 2.3</dd>

</dl>

You'll notice that with the <dl> tags you can specify more than one <dt>, and more than one <dd> per definition term. After the script runs, the tables and lists will be given this HTML structure:

Tables, after scripts.execute()
<table cellpadding="5" cellspacing="0" border="1" class="stripes">
<thead>
<tr>
<th>Heading 1</th>
<th>Heading 2</th>
<th>Heading 3</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Footer 1</th>
<th>Footer 2</th>
<th>Footer 3</th>
</tr>
</tfoot>
<tbody>
<tr class="rowA">
<td>Cell 1.1</td>
<td>Cell 1.2</td>
<td>Cell 1.3</td>
</tr>
<tr class="rowB">
<td>Cell 2.1</td>
<td>Cell 2.2</td>
<td>Cell 2.3</td>
</tr>
</tbody>
</table>
Lists after stripes.execute()
<ul class="stripes">
<li class="rowA">List Item 1</li>
<li class="rowB">List Item 2</li>
<li class="rowA">List Item 3</li>
</ul>

<ol class="stripes">
<li class="rowA">List Item 1</li>
<li class="rowB">List Item 2</li>
</ol>

<dl class="stripes">

<dt class="rowA">Definition Term 1</dt>
<dd class="rowA">Description 1.1</dd>
<dd class="rowA">Description 1.2</dd>

<dt class="rowB">Definition Term 2</dt>
<dd class="rowB">Description 2.1</dd>
<dd class="rowB">Description 2.2</dd>
<dd class="rowB">Description 2.3</dd>

</dl>

Styling the zebra stripes with CSS

With a small amount of CSS, you can style your zebra stripes.

.rowA {
background-color: #f0f0f0;
}

.rowB {
background-color: #d0d0d0;
}

You can change how tables and lists are displayed by using different CSS selectors:

tr.rowA {
background-color: #f0f0f0;
}

tr.rowB {
background-color: #d0d0d0;
}

li.rowA {
background-color: #fccccc;
}

li.rowB {
background-color: #dccccc;
}

dt.rowA {
background-color: #000;
color: #fff;
}

dd.rowA {
background-color: #505050;
color: #fff;
}

dt.rowB {
background-color: #ccc;
color: #000;
}

dd.rowB {
background-color: #d0d0d0;
color: #000;
}

Download the stripes JavaScript source code

  1. Click on the box below to highlight the code.
  2. Copy it.
  3. Paste it into a blank text file.
  4. Save it as package.stripes.js
  5. Include it in your HTML documents using a <script src=""> tag.

Who can use this script

The short answer? Anyone. It's released under the LGPL license and is free for commercial and non commercial use. Let's face it, this isn't ground breaking stuff here, so copy it. Distribute it. Use it. But always include some form of recognition and never claim it as your own. If you feel so inclined, link back to this page. If you have comments, suggestions or problems, post them below.

Friday, November 30, 2007

A Practical Guide To Numbers in JavaScript

Dealing with numbers, strings and JavaScript can be frustrating for a beginner. This is a down-and-dirty explanation of converting strings to numbers, detecting if a string is a number, and handy functions for manipulating numbers.

How many times have you gotten a number from a FORM field on your web page, tried to add it to a number only to get the wrong value back!? One of the most basic tasks of any program is converting between different data types. JavaScript is pretty flexible, but there are still some rules.

In most programming languages, a number is a primitive data type, meaning it's just a value stored in memory. In JavaScript, a number is both a primitive data type and a whole class of data and functions. Whether or not a primitive number or object-oriented number is used depends on how you are using it. A few quick examples are in order.

Creating Number Variables in JavaScript

var num1 = new Number("4");
var num2 = 4;

There are two basic ways of declaring number variables in JavaScript: By calling the Number class constructor directly using the new command, and by simply assigning a number to a variable. The most effecient method is by simply assigning a number to a variable, as this creates a primitive value number. The new Number() method creates an instance of the Number class and adds functions and properties to the number variable. The browser will try converting any data passed to the Number() function into a number, and if you're not carefull you'll get a NaN value, which means the value is Not A Number.

Once you have a number variable declared, you'll want to do some math. JavaScript gives us the basic mathematical operators, like +, -, * and / for add, subtract, multiply and divide. Below is a list of some of the mathematical operators in JavaScript:

Common JavaScript Mathematical Operators
Operator Name Description
+ Addition The sum of two numbers.
++ Increment Increase the number by 1: num1++;
+= Add-to Add a number to the current value: num += 2;
- Subtraction The difference between two numbers.
-- Decrement Decrease the number by 1: num1--;
-= Subtract-from Subtract from the current value: num1 -= 2;
* Multiplication The multiplication of two numbers
/ Division Division of two numbers
% Modulus Returns the remainder of the division of two numbers

All the operators work as expected except for the addition operator. In JavaScript, the + operator returns the sum of two numbers, or joins two strings together. The following code example illustrates when the browser knows whether to join a string or perform a mathematical operation:

Using + to join two strings, or add two numbers

var sum1 = 5 + 2;
var sum2 = 5 + "2";

alert(sum1); // Shows the number 7
alert(sum2); // Shows the string "52"

When you add a string to a number, the browser automatically converts the number to a string, and then joins the two strings. The operation 5 + "2" is interpretted by the browser as "5" + "2", joining the two strings together and converting the end result into a string. This is an easy way to convert a number to a string, but is little help for converting a string to a number.

In most programming languages, you have two basic types of numbers: Integers, which represent whole numbers like 1, 8 or -3; and floating point numbers like 33.8 and -6.5. JavaScript uses floating point numbers with up to 16 decimal places.

While the addition operator adds two numbers or joins two strings, the subtraction operator only subtracts two numbers. The browser also makes your life easier by automatically converting data types:

var diff1 = 5 - 2;
var diff2 = "5" - 2;

alert(diff1) // Shows the number 3
alert(diff2) // Shows the number 3

When you try subtracting a number from a string, the browser automatically converts the string to a number, and then performs the mathematical operation.We've seen how mathematical operators can convert between strings and numbers, but you aren't limited to those methods. There are many more which can be used for different reasons.

Converting Strings to Numbers

There are four main ways to convert a string to a number: The parseFloat, parseInt and Number functions, and subtracting a number from a string. First, let's revisit the Number() function.

Earlier we learned the Number() function takes a value and converts it to a number, and is also the class constructor used for all number variables in JavaScript. It seems like the obvious choice, but let's take a closer look.

var num1 = Number("2.25");
var num2 = Number("Price: $2.25");
var num3 = Number("three");
var num4 = Number("200.8 dollars");

alert(num1); // Shows the number 2.25
alert(num2); // Shows NaN (Not A Number)
alert(num3); // Shows NaN
alert(num4); // Shows NaN

Right away we can see a problem. The Number() function successfully converts a string to a number only as long as the string only contains numeric characters (those being 0 through 9, the decimal point or period, and the negative sign or minus). If there are any other characters, the Number function returns a NaN value, which literally means "Not A Number." If we have full control of what gets put in a string variable, the Number() function may be the right choice, but if you are taking input from the user, we may get non-numeric characters. We need a way to remove non-numeric characters from a string, then convert them to a number.

Cleaning and converting strings to numbers using the parseFloat() and parseInt() functions

JavaScript gives us two built in functions that help remove non-numeric characters from a string. The parseFloat function returns a floating point number, or decimal number, and the parseInt function returns an integer number. Later on we'll see why special care must be taken with the parseInt function.

Using parseFloat
var num1 = parseFloat("2.25");
var num2 = parseFloat("Price: $2.25");
var num3 = parseFloat("three");
var num4 = parseFloat("200.8 million dollars");

alert(num1); // Shows the number 2.25
alert(num2); // Shows NaN (Not A Number)
alert(num3); // Shows NaN (Not A Number)
alert(num4); // Shows the number 200.8

We run into a similar problem with parseFloat that we have with the Number function. If the string contains non-numeric characters before the numeric characters, a NaN value is returned. It does, however, remove non-numeric characters after the numeric characters, and returns a floating point number. Likewise, if only numeric characters are in the string, a floating point number is returned. Lastly, if no numeric characters exist in the string, a NaN value is returned.

Using parseInt
var num1 = parseInt("2.25");
var num2 = parseInt("Price: $2.25");
var num3 = parseInt("three");
var num4 = parseInt("200.8 million dollars");
var num5 = parseInt("0x10");

alert(num1); // Shows the number 2
alert(num2); // Shows NaN (Not A Number)
alert(num3); // Shows NaN (Not A Number)
alert(num4); // Shows the number 200
alert(num5); // Shows the number 16

The parseInt function works exactly the same as the parseFloat function, with two notable differences: It returns a number rounded down to the next integer, and it doesn't always return NaN when you give it non-numeric characters. In fact, it appears quite confusing. Giving parseInt the string "0x10" returns the number 16 ... um ... what? Sixteen? Giving it the characters zero-x-one-zero is equal to the number 16? Yes. The parseInt function takes up to two parameters: The string to convert to a number, and an optional second parameter specifying the number system the string should be interpretted as, called the radix. When using parseInt, always pass 10 as the second parameter, meaning the parseInt function should interpret the string as a base 10 number. A base 10 number uses the numerals 0 through 9, and every tenth value is put at a new decimal spot. We don't have a number to represent the number ten, instead we use 10: We have one group of ten, and zero groups of one.

When the string passed to the parseInt function begins with "0x" and no second parameter is given, the parseInt function treats the string like a base 16, or hexadecimal number. The hexadecimal number 10 is equal to the decimal number 16. In hex, you have one group of 16, and zero groups of 1. The equivalent number in the decimal or base 10 system is 16: one group of ten, and 6 groups of one.

The proper way to use parseInt
var num1 = parseInt("2.25", 10);
var num4 = parseInt("0x10", 10);

alert(num1); // Shows the number 2
alert(num2); // Shows NaN (Not A Number)

In the example above, the parseInt function is told that the string should be treated as a base 10 number, and thus "0x10" is Not A Number (NaN). Since humans use the decimal system when counting, you should always pass 10 as the second parameter to the parseInt function. If 10 is not passed as the second parameter and a user accidentally types in 0x15 instead of 0.15, you would get 21 instead of the number zero, and your calculations will be incorrect.

Number, parseFloat and parseInt Summary
Function Parameters Return Value
Number String[required] Number or NaN if the string contains non-numeric characters.
parseFloat String[required] Number or NaN if the string contains non-numeric characters.
parseInt String[required], Number[optional] The first parameter is a string, and the second parameter is a number. The second parameter is optional, and is the number system the string should be interpretted as. Use 10 unless you want a different number system. An integer rounded down to the nearest number is returned, or NaN if any non-numeric characters are found.
toNumber(): Clean strings and convert them to numbers

The parseFloat and parseInt functions still don't allow us to remove all non numeric characters. Unfortunately, JavaScript doesn't have a function for this, so we'll build one. You can probably search the Internet for something similar, and many times it is named toNumeric or toNumber.

Function definition for toNumber
/**
 * toNumber  Converts a string to a number
 *
 * @param   string (required)   String to convert to a number
 * @param   bool   (optional)   Make it an integer and drop decimal
 * @param   bool   (optional)   Round number up or down to nearest int?
 * @return  float, int or NaN   Number, or null if string not a number
 */
function toNumber(str, isInteger, roundNum) {
  var num;
  var strType = typeof(str);
  
  if (strType == "string") {
    // Strip non-numeric chars and convert to number
    num = Number(str.replace(/[^0-9-.]/g, ""));
  } else if (strType != "number") {
    // Return NaN if not a number
    return NaN;
  }
  
  if (isNaN(num)) {
    return NaN;
  } else if (isInteger) {
    return Math.floor(num);
  } else if (roundNum) {
    return Math.round(num);
  } else {
    return num;
  }
  
}

The toNumber function removes non-numeric characters from a string, and also allows you to specify whether or not the number is a floating point number or integer, and if it should be rounded to the nearest integer instead of dropping the decimal all together. If the string passed to the toNumber function contains no numeric characters, a null value is returned. Let's see how we can use this function:

How the toNumber function reacts to various input
var num1 = toNumber("2.25");
var num2 = toNumber("Price: $2.25");
var num3 = toNumber("200.8 million dollars");
var num4 = toNumber("103.8", true);
var num5 = toNumber("103.8", true, true);
var num6 = toNumber("no numbers here");

alert(num1); // Shows the number 2.25
alert(num2); // Shows the number 2.25
alert(num3); // Shows the number 200.8
alert(num4); // Shows the number 103
alert(num5); // Shows the number 104
alert(num6); // Shows null

As mention earlier, users can type whatever they want into form fields, so you must clean the data before you can do any math with it. The toNumber function does several things:

  1. If a number variable is given, the number is returned as a floating point, integer or rounded floating point number.
  2. Removes all non-numeric characters from the string, both before and after the number characters.
  3. You can specify if the number is an integer, and also if it should be rounded up or down to the nearest integer
  4. If the string variable given does not contain numeric characters, a NaN value is returned. You can outright test for a NaN value if you want to tell the user to enter a number.

Let's create a sample script that shows how to use this function.

Using toNumber to check for user errors
// Ask the user to enter a number
var userNum = prompt("Type a number:");

// Convert what the user typed into a number
var num = toNumber(userNum);

if (isNaN(num)) {
  // Error: User typed no numeric characters
  alert("Only numeric characters are allowed");
} else {
  // User typed at least one number, now show the sum
  num = num + 8;
  alert("The sum is " + num);
}

This shows the basic process for taking data from the user, cleaning it, and detecting possible errors.

  1. The variable userNum is gotten from the user by way of the prompt function. The prompt function returns a string of what the user typed into the prompt pop up box.
  2. We pass the userNum variable to the toNumber function.
  3. The toNumber function returns a value, and is assigned to the num variable.
  4. If the num variable is equal to null, then the user didn't type in any numeric characters. Show an error message.
  5. Otherwise, the num variable is a number, and we can show the sum.
Drawbacks to the toNumber() function

At first this function seems like a great idea — remove all non-numeric characters from the string, and then convert it to a number. What if the user accidentally types "5/8" when they meant to type in "5.8"? The toNumber function would return the number 58, which is vastly larger than the 5.8 the user intended on entering. It's up to you as the programmer to decide if this fault tolerance is acceptable in your script. While posting on WebDeveloper forums, I got involved in a discussion about converting strings to numbers. In order to reduce the impact of user errors on your script, the following basic algorithm is recommended when converting strings to numbers:

  1. Convert the string to a number first. Use the Number() function.
  2. Test the converted number to ensure it is actually a number using the isNaN() function.
  3. If the number is Not A Number, alert the user to his or her error and stop processing.
  4. If the number is a number, then continue processing.
Using isNaN() to detect non-numbers

The function definition for toNumber introduced us to a new number-related function: isNaN. The isNaN function stands for "IS Not A Number" and is a native function to JavaScript. It takes a string or number as a parameter and will return true if any non-numeric characters are found in the case of a string, or if the number passed is a NaN value. In addition, if you were to use the Number, parseFloat or parseInt functions to convert a string to a number, and a NaN value is returned by any of those functions, isNaN will return true.

The isNaN function is complimentary to parseFloat and parseInt, and is a direct compliment to Number. When the Number function returns an actual number, the isNaN function returns false. When the Number function returns a NaN value, the isNaN function returns true. Be aware that there are times when a string passed to isNaN will return true, meaning the string is not a number, when that same string passed to either parseFloat or parseInt will return a number. Always convert strings to numbers first, then test using isNaN.

How isNaN reacts to various input
var num1 = isNaN("2.25");
var num2 = isNaN("Price: $2.25");
var num3 = isNaN("three");
var num4 = isNaN("200.8 million dollars");
var num5 = parseFloat("no numbers"); // Returns NaN value

alert(num1); // Shows false
alert(num2); // Shows true
alert(num3); // Shows true
alert(num4); // Shows true
alert(isNaN(num5)); // Shows true

If you don't want to use the toNumber function above, use the Number function to convert a string to a number, then use the isNaN function to check for a NaN value.

Using isNaN to check for user errors
var userNum = prompt("Enter a number:");
var num = Number(userNum);

if (isNaN(num)) {
  alert("Only enter numeric characters.");
} else {
  num = num + 8;
  alert("The sum is " + num);
}

We've learned several ways to convert strings to numbers and account for user errors. We know that parseFloat gives us a floating point number, and parseInt gives us an integer, but parseInt drops the decimal all together. The number 8.9 becomes 8, when it should be 9 when rounded to an integer. We need a way to create a rounded integer, and the Math object provides us with a round function.

Rounding numbers properly using Math.round

Any floating point number whose decimal is less than .5 must be rounded down. If the decimal is .5 or greater, the number should be rounded up. This was something we learned in grade school, but the parseInt function failed that class. It just drops the decimal place all together. Let's use Math.round instead.

var num1 = Math.round(3.75);
var num2 = Math.round(3.33);

alert(num1); // Shows the number 4
alert(num2); // Shows the number 3

Since the decimal in 3.75 is greater-than or equal-to .5, the Math.round function returns the integer number 4. The number 3.33 has a decimal less than .5, so the Math.round function returns the integer number 3. Math.round only returns integers, however. If you want to round a number to a certain decimal place, we've got to use a native function to all Number variables in JavaScript, which we will use in our next example.

A simple form to calculate the tax

We've explored several methods of converting strings to numbers, and dealing with invalid data. Now let's create a practical example using the methods outlined above, and introduce one more method for formatting the display of numbers. First, let's create our HTML form:

Markup for our tax form
<form method="get" action="" id="frmTax">
  Price: <input type="text" name="price" value="">
  <br>
  Tax Rate: <input type="text" name="taxRate" value="0.06">
  <br>
  <input type="button" value="Calculate Tax"
      onclick="calcTax(this.form)">
</form>

It's a pretty simple form. Before some of you scream, "It's not accessible! Burn in Hell!" know that this is just a simple example. Web accessibility is beyond the scope of this tutorial. We've got two text fields: One for the price, and another for the tax rate. Lastly, we have a button that calls the calcTax function onclick, and passes a Document Object Model node reference to the FORM tag.

Example 1: Function definition for calcTax, using toNumber()
function calcTax(form) {
  var price = toNumber(form.elements["price"].value);
  var taxRate = toNumber(form.elements["taxRate"].value);
  var total = 0;
 
  if (isNaN(price)) {
    alert("Only enter numeric characters for the price");
    return;
  } else if (isNaN(taxRate)) {
    alert("Only enter numeric characters for the tax rate");
    return;
  } else {
    total = price + (price * taxRate);
    alert("The total price is: $" + total.toFixed(2));
  }
}
Example 2: Function definition for calcTax, using native functions
function calcTax(form) {
  var price = Number(form.elements["price"].value);
  var taxRate = Number(form.elements["taxRate"].value);
  var total = 0;
 
  if (isNaN(price)) {
    alert("Only enter numeric characters for the price");
    return;
  } else if (isNaN(taxRate)) {
    alert("Only enter numeric characters for the tax rate");
    return;
  } else {
    total = price + (price * taxRate);
    alert("The total price is: $" + total.toFixed(2));
  }
}

Now is a good time to note that the values of form fields are always strings. A user might type a number into a text field, but JavaScript still sees it as a string, which is where the toNumber, isNaN, Number, parseFloat and parseInt functions come into play. A closer look at both examples shows few differences in how they are written. The main difference is the level of tolerance for user generated errors. In Example 1, the toNumber function is used. This function removes all non-numeric characters from the string. If a user accidentally types a non numeric character before any numeric characters in one of the text boxes, the toNumber function is able to recover from that and still return a number.

In Example 2, if the user enters even one non-numeric character before a numeric character in one of the text fields, the calcTax function will alert the user of an error. In each example, the line of code with the alert() function call contains another golden nugget for working with numbers: the toFixed method of all Number variables.

Using the toFixed method to format numbers

This function is available for any Number variable in JavaScript, and allows you to specify how many decimal places you want to show. This function returns a string, and so most times is only usefull for displaying output to a user. It takes one parameter, an integer number of how many decimal places should be displayed.

var num = 8.75249;

alert(num.toFixed(0)); // Shows the string 9
alert(num.toFixed(1)); // Shows the string 8.8
alert(num.toFixed(2)); // Shows the string 8.75
alert(num.toFixed(3)); // Shows the string 8.752
alert(num.toFixed(4)); // Shows the string 8.7525

The toFixed function works similar to the Math.round function. In the first example, passing zero to toFixed returns 9, which is the same number that Math.round would return. The difference here is anything the toFixed function returns is a string, not a number.

In the second example, the number 1 is passed, so only one decimal place is shown. The decimal place just after .7, is .05. This .05 is rounded up to .1, and added to .7 to give a final string of "8.8". There may be a time, however, when you need to round a number to a certain number of decimal places and still have it be a number.

The round() function

There is no native round function in JavaScript that takes a number, rounds it to a certain number of decimal places, then returns a number. We'll call this function round().

Function definition for round()
/**
 * round    Rounds a number to X decimal places
 * @param   number (req)      Number to round. May also be string
 * @param   int (opt)         Number of decimal places. Optional.
 * @return  mixed             Null if NaN, or the rounded number
 */
function round(num, decimals) {
  var multiplier = Math.pow(10, decimals);
  if (typeof(num) != "number") {
    return null;
  }
  if (typeof(decimals) != "number") {
    var decimals = -1;
  }
  if (decimals > 0) {
    return Math.round(num * multiplier) / multiplier;
  } else if (decimals == 0) {
    return Math.round(num);
  } else {
    return num;
  }
}

The round() function takes up to two parameters. The first is the number that should be rounded. The optional second parameter is the number of decimal places that should be rounded to. If this second parameter is omitted, the full floating point number is returned. If zero is passed as the second parameter, then the number is rounded up or down properly to an integer. If the second parameter is 1 or more, then the number is rounded to that many decimal places. The function returns an actual number, but if you give the round() function invalid data, null is returned.

Problems with round()

Normally you should only round a number to a decimal place when ouputing the number to the user. The function you'd want to use is the toFixed method of the Number class. If a calculation algorithm explicitly calls for a rounded number, then using the round() function above would be acceptable. If your algorithm does not explicitly call for a rounded number, then only round the number when outputing it to the user.

Preventing user number entry errors with textFieldToNumber

So far we've discussed converting strings to numbers, and accounting for user errors, but wouldn't it be nice to have a function that prevents user errors from ever occuring? Let's create one more function that cleans non-numeric characters from a string, and then apply it to a form text field.

function textFieldToNumber(el) {
  el.value = el.value.replace(/[^0-9-.]/g, "");
  el = null;
}

It's a pretty simple function. It strips out non numeric characters with a regular expression and puts the stripped down string back into the text field. Putting a number into a form field automatically converts it to a string. As you recall from earlier, form field values only contain strings. Next, we just need to attach the textFieldToNumber function to a form field.

<form action="" method="get">

<input type="text" onblur="textFieldToNumber(this);" name="age">

</form>

When the user presses the tab key or clicks off the form field, the onblur event fires, and then executes the textFieldToNumber function. A document object model node reference to the form field is passed to the function using the keyword this. The function removes any non numeric characters from the form field value, and then puts the pure number back in the form field. Since JavaScript can be disabled, make sure any server side script that receives data from your form double checks the input.

A Quick Recap

We've gone over a lot so far. Numbers in JavaScript are primitive and complex data type. The Number class has more functions and data members than this tutorial covers. Mathematical operators in JavaScript don't always do math. The addition operator (+) also joins two strings, and if you try adding a number to a string, the browser automatically converts the number to a string, and then joins the two strings.

We also learned the four main ways of converting strings to numbers: by subtracting a number from a string, using the Number class constructor, using the parseFloat function or using the parseInt function. The parseInt function needs the radix, or number base, passed to it each time. You might pass it a base 10 number with some funky input and get back a base 16, or hexadecimal number.

The toNumber function was written to convert strings to numbers in a more flexible manor than parseFloat and parseInt. The native isNaN function can detect if a variable doesn't contain a number value, and is used for detecting user errors in the data. Once valid numbers have been tested for, we touched on how to round numbers using Math.round and the toFixed method of the Number class. We also created a round() function that returns a number rounded to a certain decimal place, rather than using the toFixed method which returns a string. Lastly, the custom function textFieldToNumber() can be attached to a form field and prevents user errors when entering numbers.

There is much more about numbers and JavaScript that this tutorial hasn't covered, however these are the basics and all you should need 90 percent of the time. Other useful resources are listed below:

More Information on Numbers in JavaScript

Wednesday, October 17, 2007

JavaScript, DOM, and the Humble FORM

Use standard DOM methods and properties to interact with HTML forms.

This is a basic tutorial on using JavaScript to interact with forms on a Web page. It covers two methods. The first method is very common, but was first implemented in old browsers. The second method uses standard Document Object Model methods and properties, and is considered best practice.

Using document.forms to Get DOM References to FORMs

Old browsers polluted the document object with multiple, redundant references to FORM tags. The document.forms array actually contains two parts. First, it is an integer-indexed array of references to FORM tags, and secondly it is an associative array. Let's take the following HTML as an example:

Example 1
<form name="establishment_form">
  ...
</form>

<form>
  ...
</form>

In our pretend HTML document, we've got two forms. The first form is given a name and the second form is not given a name. You can access the Document Object Model nodes that represent those FORM tags like this:

Example 2: Getting DOM node references to the two forms in Example 1
var form1 = document.forms["establishment_form"];
var form1 = document.forms.establishment_form;
var form1 = document.forms[0];

var form2 = document.forms[1];

You'll notice that the first form on the page, which was given a name, can be accessed in three ways: document.forms["establishment_form"], document.forms.establishment_form, and document.forms[0]. They all represent the same Document Object Model node and the same HTML tag.

The second form on the page was not given a name, and can only be accessed using document.forms[1]. Also remember that integer-indexed arrays start with zero, so an index of zero is the first form, and an index of 1 is the second form.

One more wrench is thrown into this cog wheel since the first form was given a name: It can also be accessed via document.establishment_form. So a quick recap of how to get a node reference to the first form tag in our HTML example above (example 1).

Example 3: Getting a DOM node reference to the first form in Example 1
var form1 = document.establishment_form;
var form1 = document.forms["establishment_form"];
var form1 = document.forms.establishment_form;
var form1 = document.forms[0];

Yuck. Writing JavaScript for older browsers basically sucks. All of these different methods existed because browser developers couldn't agree on the best way to do things. Now enter Web Standards.

Standard Methods for Getting DOM Node References to FORMs

There are three main ways of getting a reference to a form tag using standard methods. Let's use our HTML code from example 1 again, slightly modified.

Example 4.
<form id="establishment_form">
  ...
</form>

<form>
  ...
</form>

An Id is used instead of the name attribute. Using an Id is best because it identifies one element on the page. Names can be applied to more than one element, thus the document.forms.form_name method might reference a different form than you think. Having more than one form with the same name will cause JavaScript to return a reference to the first instance of a form with the given name. Furthermore, if you use Cascading Style Sheets, giving Ids to all of your FORMs allows you to easily style forms individually.

Example 5: Standard DOM methods to get references to FORM tags
var form1 = document.getElementById("establishment_form");
var form1 = document.getElementsByTagName("form")[0];

var form2 = document.getElementsByTagName("form")[1];

We still have more than one way to grab a reference to the first FORM in Example 4. The advantage of using document.getElementById over document.getElementsByTagName is if you ever add a FORM to that page, the index number at which the establishment_form FORM tag is referenced may change. Using document.getElementById allows you to physically place that FORM tag anywhere in the HTML document and still be able to grab a reference to that FORM tag without changing your JavaScript. The document.getElementById method is recommended for grabbing DOM node references to FORM tags. Now that we have a node reference to our FORM tag, we need node references to the form fields.

Getting Node References To Fields Within A Form

We'll keep using the HTML from Example 4, and yet again we will expand it.

Example 5: Adding some form fields
<form id="establishment_form">
  <input type="text" name="title">
 
   <textarea cols="50" rows="5" name="description"></textarea>
 
   <input type="submit" value="Add Establishment">
</form>

We'll forgo the labels for each form field, as they aren't important for this tutorial. Right now we have two form fields and one button. The form fields are given a name, and the button is given no name. Let's see how we can access those form fields with JavaScript. First, let's get a node reference to our form:

var form = document.getElementById("establishment_form");

Now that we have a node reference to our FORM tag, let's grab references to each form field using the deprecated methods first implemented by older browsers:

Example 6: Get DOM node references to form fields the old way
// Get a reference to the FORM tag
var form = document.getElementById("establishment_form");

// Use old method of getting references to fields
var title = form.title;
var description = form.description;

That wasn't too difficult. When you give a form field a name, the FORM tag gets a property named the same thing as the form field. This is fine and well, but an inherent property of a FORM tag is also called title. This would be the value of the title attribute of the FORM tag. We have a form field named title, which then overwrites the FORM tag's title property with a node reference to the form field. That's not so good. Suddenly we have a problem. The name space of the FORM tag is quickly polluted with references to the form fields inside. There is a better way.

Example 7: Get DOM node references to form fields the standard way
// Get a reference to the FORM tag
var form = document.getElementById("establishment_form");

// Use standard method for getting references to fields
var title = form.elements["title"];
var description = form.elements["description"];

Each FORM tag has a handy property called elements which holds references to all the form fields. This is a two part property. It contains an integer-indexed array of references to all the form fields, and an associative array of references. Using the elements property, you can get a reference to the "title" form field in three ways:

var title = form.elements["title"];
var title = form.elements.title;
var title = form.elements[0];

You can supply the name of the form field or the index at which it appears in the FORM tag HTML source code. Since the "title" INPUT tag is the first field in the source code, it resides at form.elements[0]. Now think about the future. Maybe you rearrange the form somewhere down the line, and the "title" INPUT tag is no longer the first form field. You can no longer access that field in JavaScript with form.elements[0], so it's always safest to use the form.elements["field_name"] syntax for the same reason it's best to use document.getElementById. It is considered best practice to use form.elements["field_name"].

The table below outlines the properties in the form.elements property.

Example 8: Properties of form.elements
Property Type Description
0 object The "title" form field
1 object The "description" form field
2 object The submit button
length number The number of form fields or elements in this form
title object The "title" form field
description object The "description" form field

Now let's add form fields with the same name: a group of radio buttons.

Example 9: Adding more than one field with the same name
<form id="establishment_form">
  <input type="text" name="title">
 
  <textarea cols="50" rows="5" name="description"></textarea>
 
  <input type="radio" name="estab_type" value="General">
  <input type="radio" name="estab_type" value="Restaurant">
  <input type="radio" name="estab_type" value="Government">
 
  <input type="submit" value="Add Establishment">
</form>

We've added a group of radio buttons, all named "estab_type". This changes the object model of the FORM tag.

Example 10: Properties of form.elements
Property Type Description
0 object The "title" form field
1 object The first "estab_type" radio button
2 object The second "estab_type" radio button
3 object The third "estab_type" radio button
4 object The "description" form field
5 object The submit button
length number The number of form fields or elements in this form (includes all non named elements like buttons)
title object The "title" form field
estab_type array[] Integer-indexed array of references to each radio button.
description object The "description" form field

Now you can see why using the form.elements[n] syntax isn't the best. The order in which the form field appears in the HTML source determines the index number in the form.elements array at which a form field resides. The "description" text field went from index 1 to index 4. If you had used form.elements["description"] to get a reference to the "description" text field, this change to the FORM markup wouldn't have affected your JavaScript.

Now we've run into another unique case. The form.elements["estab_type"] property is an array, not a reference to a form field. Remember that we had three radio buttons named "estab_type". The browser accounts for this by mashing references to all three radio buttons in form.elements["estab_type"]. Let's take a look at the contents of this array.

Example 11: Properties of form.elements["estab_type"]
Property Type Description
0 object Reference to first "estab_type" radio button
1 object Reference to second "estab_type" radio button
2 object Reference to third "estab_type" radio button
length number Number of items in this array.

The object model of the form.elements property then looks like this:

Example 12: Breakdown of the form.elements object model
form.elements[0]      // "title" INPUT
form.elements[1]      // "estab_type" radio 1
form.elements[2]      // "estab_type" radio 2
form.elements[3]      // "estab_type" radio 3
form.elements[4]      // "description" TEXTAREA
form.elements[5]      // submit INPUT
form.elements.length  // Number of form elements
form.elements.title   // "title" INPUT

form.elements.estab_type[0]      // "estab_type" radio 1
form.elements.estab_type[1]      // "estab_type" radio 2
form.elements.estab_type[2]      // "estab_type" radio 3
form.elements.estab_type.length  // Number of radio buttons

form.elements.description  // "description" TEXTAREA

// Note: form.elements.estab_type is the same as
// form.elements["estab_type"]

Since we can reference form fields by their name and by an index number, we can do bulk processing on a form using JavaScript. It's easy to loop through all the fields in a form.

Example 13: Looping through all the form fields
// Get a DOM reference to our form
var form = document.getElementById("establishment_form");

// Loop through all the form fields
for (var i = 0; i < form.elements.length; i++) {
  alert(form.elements[i]);
}

Also recall that our "estab_type" radio buttons formed an array in form.elements["estab_type"]. We can also loop through this with JavaScript:

Example 14: Looping through an array of form fields with the same name
// Get a DOM reference to our form
var form = document.getElementById("establishment_form");

// Loop through all the "estab_type" radio buttons
for (var i = 0; i < form.elements["estab_type"].length; i++) {
  alert(form.elements["estab_type"].value);
}

A Quick Recap

We've covered how to get a DOM node reference to a FORM tag, and also how to get references to the form fields inside of it. There were two basic methods. One used the API of older browsers, which included document.forms, and form.field_name, and the other utilized standard Document Object Model functions and properties like document.getElementById and form.elements. Using the standard DOM properties allows you to change and rearrange your FORM markup down the road with little fear of breaking your JavaScript, and since you are using standard functions and properties, your script will be supported for many years to come.

Methods of getting DOM references to FORMs and form fields
Standard Method (Best Practice) Old Method (Deprecated)
document.getElementById("form_id") document.forms.form_name
document.getElementById("form_id") document.form_name
document.getElementsByTagName("form")[n] document.forms[n]
form.elements["description"] form.description
form.elements.description form.description

Additional References

Monday, October 8, 2007

iPhone: Killing or Redefining Web Standards?

The iPhone will be looked upon as the epoch of the truly mobile web. It doesn't support handheld media style sheets and tries to display web sites normally, as well as tailoring them to its lower resolution. The iPhone may destroy a Web standard or cause it to be redefined.

The World Wide Web Consortium has set up a number of different types of media on which a Web page can be displayed. Chief among them are the "screen" and "handheld" media. Desktop web browsers fall into the "screen" category and things like cellphones and PDAs fall into the "handheld" category. Handheld devices must adapt content designed for the screen media to their less capable displays.

Apple's Safari Web browser on iPhone isn't the first browser that attempts to display Web pages normally. Opera's flavors of mobile browsers do something similar. A while back, I believe around version 7 or 8 of its desktop browser, Opera in mobile mode would try to display a Web page how you'd see it on a desktop computer unless you imported a style sheet for the handheld media. This continues on today as "Small Screen Rendering" (SSR) and attempts to crunch a web page down in size to fit on a smaller screen.

This seems like a fairly sensible approach. The Opera browser is aware it's on a mobile or handheld device and supports hand held style sheets, but in the absence of hand held style sheets it does it's best to display a Web page not designed for a handheld device. Safari on iPhone basically claims it's a desktop browser. With a little additional markup you can change its behavior, and with a CSS3 media query you can feed that particular flavor of Safari a different style sheet. The common theme here is that mobile browsers, despite what the W3C might have imagined, try to be desktop web browsers first unless an alternative method of styling exists.

The emergence of a two-tiered Mobile Web

The World Wide Web Consortium envisioned the Web as requiring different styles based on the display capabilities of a device, but getting the same markup. Reality is panning out differently. Web page HTML has been a bitter soup of presentational markup garnished with sparse portions of meaningful tags for many years, and web browsers must display these pages. To make matters worse, most pages are designed for the screen media, or desktop Web browsers as they are more commonly known as. Browser developers seem to be taking one of two paths:

  • Attempt to display the Web page like a normal desktop browser would, and in the presence of some browser-specific markup, use styles catoring to the device. (Safari on iPhone).
  • If no handheld media style sheets are found, crunch, chew, and rearrange the page to fit the display of the device (Opera Mini and Mobile).

The mobile web is becoming two-tiered. This doesn't seem like something the W3C imagined would happen.

Upon reading that Safari on iPhone wouldn't support handheld media style sheets, I had to wonder if we'd be stuck in a mobile browser war similar to Netscape vs. Internet Explorer at the close of the last century. Looking into it further made me realize that both Opera's and Safari's compromises were out of necessity and shed light on how to realistically create a mobile Web. I'm not sure where the Web Standards explain how handheld browser developers should handle legacy Web pages or pages not designed for handheld devices, but the method for implementing handheld styles is starting to diverge. This could destroy cross-browser functionality, and presents a challenge to the industry on how to handle the non-mobile Web on mobile devices in a standardized fashion.

Even though Safari on iPhone is on a hand held device, there are many reasons that it shouldn't be thrown into the same lot as cell phone browsers. Screen resolution, processor speed, battery life and memory capacity are greater on iPhone, and therefore Safari is more capable. Opera has also divied up its handheld browser into Mini and Mobile versions assuming Mini will be on cell phones and Mobile will be on technologically beefier personal digital assistants and smart phones. Suddenly the notion of a "screen" and "handheld" web is blurred.

The multi-tiered mobile Web

In the coming months we may see a plethora of mobile devices with many levels of capabilities — screen resolutions, color depth, processor speed, memory capacity, plug-in support, battery life — all these things will improve, but at different rates and on different devices. How can we possibly design for a "mobile web" when we don't know what will be accessing it? Opera's implementation of mobile technology is closest to the spirit of the Web Standards when compared with Safari on iPhone, but design itself is resolution dependent. You need physical limits to implement good design. This is where iPhone's implementation makes the most sense.

On the iPhone, you use a CSS3 media query in which you specify the maximum resolution for the device, and you add a few META tags to your HTML. I don't believe this meshes well with Web Standards, but makes the most sense design-wise. The iPhone isn't "handheld" or "screen" or "print." It's got a maximum width of 480 pixels in landscape mode. We have 480 pixels of width to design with. Now we have a physical limitation. Now we can truly design effectively. Now there are no standards and we are open to conflicts between different devices and software platforms— precisely the situation Web Standards have sought to avoid. However, this makes the most sense for design.

There are too many levels of "hand held" or "mobile" devices to realistically mesh them together, and yet they aren't truly "screen" or "desktop" devices. They can't handle reams of JavaScript and dozens of images, but they can handle some. The W3C's media types have gotten us started. Now that we are seeing how the mobile web is developing, it's time to look back and choose a different metric on which to decide how to display content. Pixels count. The W3C has tried hard to make them count less, but in the end that's what really counts. We need a standard on how to decide which resolutions get which styles and functionality. I still don't want to write multiple versions of HTML on one Web site for God knows how many different types of devices when one version should suffice.

I've only just started down the path of designing and coding for the "mobile web," so I've got oodles more questions than answers.

Additional Resources

Wednesday, October 3, 2007

New Design Is Live!

Well, the headline pretty much says it all. I've got the new design done.

"Wait a tick!" you say. "It doesn't work in Internet Explorer. It looks like rubbish."

Yes. The design doesn't work in Internet Explorer. Truth be told, it won't ever work in Internet Explorer. That browser is old and outdated. I decided to not support old buggy browsers at all. This design uses CSS 3 styles, and some "real" web browsers don't support that yet (c'mon Opera), but that's only for rounded corners on the boxes in the layout.

So, this is my new design. This is my playground. I don't want to play with Internet Explorer. Too bad so sad.

Download a real web browser:

Monday, October 1, 2007

Pardon My Dust

I'm finally putting a real design on this. In the mean time, I call this design "An Ode To Netscape 2." Enjoy. :)