Tuesday, May 06, 2008

Keystroke and Field Validation with JavaScript

Yesterday, I provided a simple tool to demonstrate the differences between keydown and keypress events. Today, I'm going to apply that knowledge toward making a lightweight form validation library. This library will prevent invalid characters from being entered in form input elements which is useful toward preventing malicious data from being entered (think XSS), as well as ensuring data integrity (data is in the valid format, all required fields present, etc.).

Sample Form:



Alpha
Alphanumeric*
Date
Decimal
Digit
Email
Hostname
IP address
Integer
Punctuation
Real number
Time
Whitespace
custom
type
format


* denotes required field.




The form above should only allow valid characters in each input field - this is your keystroke validation. Furthermore, for fields that require a specific format, when you leave a field with invalid formatted data it should provide an immediate visual indication of the error by setting a fieldWithErrors CSS class on invalid fields. Additionally, a fv:onInvalid event is fired on the invalid element to allow custom events handlers to provide additional actions. For example, enter a number in the date field and tab out. The field will be outlined in red from the fieldWithErrors CSS class and the message, "Date must be yyyy-mm-dd" will be shown by the handling of the fv:onInvalid event. Note: the CSS class will be removed automatically when the field is corrected, and a corresponding fv:onValid event will be fired to allow the custom code to clean up after itself.

Validation rules are defined as Regular Expressions. If you are not familiar with Regular Expressions here is a good reference. Additionally, many predefined regex rules for various validations can be found at the regex library.

Several common data types and formats are defined by default in the library, but it also provide the mechanism to add you own data types. In the custom entry, the type entry allows you to specify an on-the-fly keystroke validation regular expression, and the format entry allows you to specify an on-the-fly field format validation regular expression.

The Validate Form button demonstrates the action you would want to perform before a form submission. It invokes a validateAllFields function which just runs all the field validations (and invokes corresponding CSS rules and issues events). It also returns an array of the invalid field names in case you want to build a list of errors (like the Rails flash messages).


Usage Instructions

Sample Usage:

<script src="javascript/formValidation.js"></script>
<script>
var vf = new FormValidation();
vf.addValidationForField("date", "date", "date");
</script>

addValidationForField requires 3 parameters:

  1. The identifer of the field to add validation for

  2. The name of the data type to use for keystroke validation

  3. The name of the data format to use for field validation

In this sample all the names are the same, but they usually wouldn't be.

Just doing the above will prevent invalid characters from being entered in the data field and can indicate when the field is invalid.

When a field is changed, the format validation is run and will do two things.

  1. It will mark invalid fields are the fieldWithErrors CSS class. This can be used to appropriately highlight the error to the user. For example, the following CSS rule will outline the field in red:

    .fieldWithErrors {
    border: 3px solid red;
    }

  2. An event will also be issued in the field to indicate whether or not the field is valid. The events to watch for are fv:onInvalid and fv:onValid. These events can be used to hide/show error messages such the following which will display a pre-defined error message:

    $$('input').each(function(inputElem) {
    inputElem.observe("fv:onInvalid", function() {
    $(this.identify() + "-error").show();}.bind(inputElem));
    inputElem.observe("fv:onValid", function() {
    $(this.identify() + "-error").hide();}.bind(inputElem));
    });

    Or any number of other actions. For example, you may wish to display a popup error message such as those provided by Prototype Window or add messages to an error flash area as commonly done in Rails apps or toggle the submit button as enabled/disabled. Using these events allows you to add whatever actions you desire when the
    validation fails (or passes).



Download
formValidation.js

Monday, May 05, 2008

keydown vs. keypress (in JavaScript)

In my next post, I intend to discuss a validation library which performs keystroke and field level validation for HTML forms using JavaScript and regular expressions. However, before you can correctly understand how the validation works, as well as when you can test for various validation conditions, you have to recognize the different behaviors between the keydown and keypress events.

In the following text fields, type whatever you like and notice the different behaviors in the table below it.





EventcharCodekeyCodeDisplayShifted?
Keydown
Keypress


Some notable problem keys when dealing with JavaScript keystroke validation are the arrow keys and other editing keys (delete, home, end, insert) which should be allowed in order to edit a field value, however they can appear to be punctuation symbols which may not be desired in the field. Function keys and number pad keys are also problematic.



Here are some key observations regarding keypress events:

  • Firefox sends regular keys as charCodes and special keys as keyCodes

  • Safari sends both charCode and keyCode for regular keys but does not issue keypress events for special keys

  • Internet Explorer does not send charCodes, only keyCodes, and does not issue keypress events for special keys

  • Opera does not send charCodes, only keyCodes, but also issues keypress events for special keys making it impossible to distinguish between some keys such as '-' vs. Insert and '.' vs. Delete

  • Konqueror will send both charCode and keyCode for regular keys, but only keyCode for special keys


Friday, May 02, 2008

Bluetooth Proximity Monitor (improved)

About a month ago, a friend of mine pointed out a bluetooth proximity program for windows which would lock your desktop when you walked away by polling your mobile phone's proximity to your computer. I thought that sounded fun to try as I have a bluetooth enabled phone and laptop, however, I'm running Linux, not Windows. After a quick search on Google I found a Bluetooth Proximity Monitor script for Linux. It worked pretty well as it was, but I've made a few adjustments to it to improve it for my purposes.


  1. The original script is written for KDE or other window managers that use xscreensaver. Since I'm currently running in Gnome, I had to switch the commands to use gnome-screensaver instead.

  2. While changing the screensaver commands, I also added the ability to toggle my instant messenger (Pidgin) from 'available' to 'away'

  3. The original script has a single THRESHOLD value to toggle between being near and far. I decided I needed separate NEAR and FAR thresholds. This is due to the wide variance of proximity I can have while I'm sitting at my desk. Just turning in my chair so that my body was between the phone and the laptop could change my RSSI (proximity value) from -1 to -18, so I need a fairly high threshold to prevent that. On the other hand, setting the threshold high could allow my system to unlock when I'm still 30 feet away. So making separate thresholds allows the proximity monitor not to trigger just because I turned in my chair, but also not unlock until I've actually returned to my desk.

  4. With the higher away threshold, it's possible (though uncommon) to totally leave the bluetooth range before it triggers that you've gone away. So, I also added a little logic to trigger the change in proximity if you were previously in near proximity, but now your bluetooth can no longer be pinged (out of range, turned off, etc).

  5. Finally, I alter the proximity check interval depending on if you're near or far. The motivation for this was an attempt to reduce power demands a little in order to prolong the battery life. I haven't done any actual tests to determine if it helped or not. Basically, if you're at your desk, it only checks every 5 seconds to make sure you're still there. If you've walked away it switches to check every 2 seconds in order to be more responsive to when you return.



In the end, my version of the bluetooth proximity monitor script looks like:

#!/bin/sh

DEVICE="00:1A:8A:61:6C:FE"
CHECK_INTERVAL=5
NEAR_THRESHOLD=-1
FAR_THRESHOLD=-22
PID=0
START_CMD='/usr/bin/gnome-screensaver'
FAR_CMD='/usr/bin/gnome-screensaver-command -l && purple-remote setstatus?status=away '
NEAR_CMD='/usr/bin/gnome-screensaver-command -d && purple-remote setstatus?status=available'
HCITOOL="/usr/bin/hcitool"
DEBUG="/var/log/btproximity.log"

connected=0

function msg {
echo "$1" >> $DEBUG
}

function msgn {
echo -n "$1" >> $DEBUG
}

function check_connection {
connected=0;
found=0
for s in `$HCITOOL con`; do
if [[ "$s" == "$DEVICE" ]]; then
found=1;
fi
done
if [[ $found == 1 ]]; then
connected=1;
else
# msgn 'Attempting connection...'
if [ -z "`$HCITOOL cc $DEVICE 2>&1`" ]; then
# msg 'Connected.'
connected=1;
else
msg "ERROR: Could not connect to device $DEVICE."
fi
fi
}

function check_xscreensaver {
PID=`ps -C gnome-screensaver --no-heading | awk '{ print $1 }'`
if [ "$PID" == "" ]; then
$START_CMD &
fi
}

check_connection

while [[ $connected -eq 0 ]]; do
check_connection
sleep 1
done

name=`$HCITOOL name $DEVICE`
msg "Monitoring proximity of \"$name\" [$DEVICE]";

state="near"
while /bin/true; do

check_xscreensaver
check_connection

if [[ $connected -eq 1 ]]; then
rssi=`$HCITOOL rssi $DEVICE | sed -e 's/RSSI return value: //g'`

if (( $rssi <= $FAR_THRESHOLD )); then
if [[ "$state" == "near" ]]; then
msg "*** Device \"$name\" [$DEVICE] has left proximity (signal: $rssi)"
state="far"
$FAR_CMD > /dev/null 2>&1
CHECK_INTERVAL=2
fi
elif (( $rssi >= $NEAR_THRESHOLD )); then
if [[ "$state" == "far" ]]; then
msg "*** Device \"$name\" [$DEVICE] is within proximity (signal: $rssi)"
state="near"
$NEAR_CMD > /dev/null 2>&1
$START_CMD &
CHECK_INTERVAL=5
fi
fi
# msgn "RSSI = $rssi, "
elif [[ "$state" == "near" ]]; then
msg "*** Device \"$name\" [$DEVICE] is no longer detectable"
state="far"
$FAR_CMD > /dev/null 2>&1
CHECK_INTERVAL=2
fi
# msg "state = $state, PID = $PID, sleep = $CHECK_INTERVAL"

sleep $CHECK_INTERVAL
done