SiteExperts.com Logo Home | Community | Developer's Paradise | Jobs
User Groups | Site Tools | Site Information | Search

Inside Technique : Custom Input Validation : The Code - Part I

Below is the complete checkNumber() function. We are going to take the next two pages giving you a tour through the techniques used in this code.

function checkNumber(el) {
  // Make sure IE4 by examining createTextRange method
  if (document.body==null) return true
  if (document.body.createTextRange==null) return true    
  // Convert key code to character
  var key = String.fromCharCode(event.keyCode), dotRelationship, allowDigit
  var checkDot = false
  var allowDigit = false
  // Check if number of digits following decimal point is cached
  if (el._digits==null) {
    // Examine default value (initial value attribute)
    var strNum = el.defaultValue
    // Find the decimal point
    var idx = strNum.indexOf(".")
    // Calculate number of decimal digits
    if (idx>-1) 
      el._digits = strNum.length - idx - 1
    else
      el._digits = -1
  }

  // Make sure a digit or a decimal point
  if (!isNaN(key) || key==".") {
    // Get selection (either insertion point or text selection)
    var selectionRange = document.selection.createRange()
    // Store a copy for use in comparisons
    var tempSel = selectionRange.duplicate()
    // Variable for the input range 
    var inputRange = el.createTextRange()
 
    // Get index of the dot
    var dotPos = el.value.indexOf(".")
   
    // If decimal point, check if new character valid
    if (dotPos > -1) {
      // Locate existing decimal point 
      inputRange.findText(".")
      // When user types decimal, it is only accepted if
      // replacing existing decimal point
      if ((key==".") && (el._digits>-1)) {
        if (tempSel.findText("."))
        // Test if existing decimal in selection
        checkDot = selectionRange.inRange(tempSel)
      } else {
        // Determine whether the selection is before or after the decimal point
        dotRelationship = inputRange.compareEndPoints("EndToStart",selectionRange)
        // Test if new input is an allowable digit
        allowDigit = ((dotRelationship<=0 && (el.value.length - dotPos -1 < 
            el._digits || selectionRange.text.length>0)) || (dotRelationship==1 && key!="."))
      }
    } else {
      // If inserting a decimal, test number of following digits
      if (key==".") 
        tempSel.moveEnd("textEdit",1)          
      allowDigit = (key=="." && (tempSel.text.length - 1 < el._digits)) || !isNaN(key)
    }
  }
  // Return whether the key is allowed
  return (checkDot || allowDigit)
}

The checkNumber function takes the element as an argument. This allows us to reuse the checkNumber function for any input control on the form. The this is a keyword that passes a reference of the current element or object.

The first test the checkNumber function performs is to determine whether the browser supports the createTextRange method. This helps distinguish not only between the Internet Explorer and Netscape but between the different platforms of Internet Explorer 4.0. The TextRange object is a feature that may not be supported across all platforms of Internet Explorer. The test is performed in two steps: first we test if the body object is supported (this eliminates Netscape), and second we test whether the TextRange object is supported.

  // Make sure IE4 by examining createTextRange method
  if (document.body==null) return true
  if (document.body.createTextRange==null) return true

If this test passes, then we start the real work. The first test is to see if a number of decimal point was actually entered. If not, then we should not spend any cycles actually testing the key:

    var key = String.fromCharCode(event.keyCode), dotRelationship, allowDigit
    if (!isNaN(key) || key==".") {
      // ...
    }

The keyCode property on the event object returns the ASCII value of the typed key. The fromCharCode converts this ASCII code to the actual string character. Next we use isNan() function tests whether the string is also a valid number or check if the key is a decimal point. If neither are true, we don't event process the value.

If you looked at the complete function presented earlier, you will notice there is a small amount of script that runs before the test. To speed up the overall processing. we cache the number of digits following the decimal point during the first key press. We store this information on the element itself. This technique takes advantage of the array nature of all objects in JavaScript. You are allowed to add new properties and functions to any object in JavaScript, including built-in ones. In this example, we added the property, _digits, to the input element if it does not yet exist. Existance is tested by comparing the property to null:

    // Check if number of digits following decimal point is cached
    if (el._digits==null) {
      // Examine default value (initial value attribute)
      var strNum = el.defaultValue
      // Find the decimal point
      var idx = strNum.indexOf(".")
      // Calculate number of decimal digits
      if (idx>-1) 
        el._digits = strNum.length - idx - 1
      else
        el._digits = -1 // no decimal point
    }

The _digits property is calculated by examining the defaultValue property. The defaultValue property corresponds to the persisted value attribute (the value stored in your HTML file). From this value, we calculate how many digits actual follow the decimal point. This is used when we test the user's input.

So far you have seen the code that sets up the real work. Next we walk through using the TextRange object to examine the user's input and compare it to the value already being displayed.