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

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

Now we are ready to examine the code that tests the user's input. The code examines the following information:

  1. Get the current selection.
  2. Get the location of any existing decimal points.
  3. Test if the decimal point is in the selection. If so, new decimal points can be entered.
  4. Test if the selection is before the decimal point. If so, allow new digits only.
  5. Test if the selection follows the decimal point. If so, check if the new digit is permitted.
  6. If no decimal point and one is entered, make sure there are not too many digits following the insertion point.

Doing all of these tests is actually fairly complicated. Since we are working with the text selection, we use the TextRange object for all our manipulations. The first step is to get the current selection. The selection is exposed through the createRange() method on the document's selection object. This method returns a TextRange that represents the selected text or the location of the insertion point in the text box. We also create a TextRange that stores the entire value of the text box. This is retrieved using the createTextRange method that is exposed on the text box itself:

  // 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()

As we continue through the code, the most important point to remember about the TextRange object is that there is no concept of character positions. If you look at all the tests, they all talk about comparing the position of the decimal point in relation to the user's input. With character positions, this would be relatively simple as you would compare two numbers. However, with TextRange objects, you can only test the position relative to another range using the built-in methods. Therefore, the remainder of this code works fairly abstractly by examining whether text ranges are before, after, or within other text ranges.

The rest of the code revolves around testing the input against the decimal point. For example, if a decimal point exists in the text box, and a new decimal point is typed, it can only be accepted if the existing decimal point is selected by the user so it is replaced. We perform this test by moving the TextRange to the existing decimal point. Then we call the inRange() method to see if the range representing the existing decimal point is contained within the range representing the user's selection.

If a number is entered, we perform a different comparison. The current insertion point is compared against the position of the decimal point. If the decimal point is before the insertion point, we need to test if there are any decimal places left to fill. If the decimal point follows the insertion point, the user's input is automatically accepted.

If no decimal point already exists and a decimal point is entered, it is tested to make sure that inserting the decimal point does not cause too many decimal places to be created. If not, the decimal point is inserted. Last, if no decimal point exists and a number is typed, it is automatically accepted.

The explanation for these steps is actually longer than the code! Below is the code fragment that performs all the actual testing:

  // 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)
  }

The last step is to return a boolean to the event handle signifying whether the typed key is to be accepted. If you have been following the code closely, you will notice that if either the variable checkDot or allowDigit is true, then the typed key is accepted:

    // Return whether the key is allowed
    return (checkDot || allowDigit)

That concludes the code. We recommend you play with the code and the TextRange object to better familiarize yourself with these techniques and concepts. On the next page, we provide some parting thoughts and provide a brief explanation on why you can't write a cross-browser version of this code.