1 /** 2 * The NornixFormHandler class makes it easy to do form input validation and other 3 * common form tasks. 4 * 5 * Invalid fields will generate a dialog box with an error message, 6 * and the corresponding form fields will get the class "invalid". 7 * 8 * It is easy to add your own field validators. 9 * 10 * Usage instructions at http://forms.nornix.com/info/usage 11 * 12 * Warning: some strings used in the scripts will only function properly 13 * if it is served as UTF-8. If your HTML document is encoded using UTF-8 14 * this will happen automagically. See the usage instructions. 15 * 16 * @author Anders Nawroth <http://www.anders.nawroth.com/> 17 * @author Eric Jexén <http://eric.jexen.se/> 18 * @license LGPL <http://forms.nornix.com/license> 19 * @copyright 2006--2007 20 * @version @version@ 21 * @constructor 22 */ 23 Nornix.FormHandler = function () 24 { 25 /** 26 * Variable to work around ECMAScript problems with the "this" keyword inside functions. 27 * @type Object 28 * @private 29 */ 30 var thiz = this; 31 /** 32 * Simple RegExp to find validator class names. 33 * Error checking on the names is executed in the checkForms method. 34 * @type RegExp 35 * @private 36 */ 37 var searchValidate = /validate(\S)+/g; 38 /** 39 * Simple RegExp to find "confirm" in class names. 40 * @type RegExp 41 * @private 42 */ 43 var searchConfirm = /(^| )confirm( |$)/; 44 /** 45 * Simple RegExp to find "okMessage" in class names. 46 * @type RegExp 47 * @private 48 */ 49 var searchOkMessage = /(^| )okMessage( |$)/; 50 /** 51 * Objects to keep track of the focused form element, if any. 52 * The lastFocuesElement won't get cleared. 53 * @type Object 54 * @private 55 */ 56 var focusedElement = null; 57 /** 58 * Pattern to recognize "message" class. 59 * @type RegExp 60 * @private 61 */ 62 var searchMessage = /(^| )message( |$)/; 63 /** 64 * Pattern to recognize "-cancel" in name attribute of submit button. 65 * @type RegExp 66 * @private 67 */ 68 var searchCancel = /.*-cancel$/; 69 70 // initialize when page is loaded 71 Nornix.events.add(window, 'load', init); 72 73 /** 74 * Initializes the NornixFormHandler object. 75 * Searches thru the document, and adds event handlers for the submit and reset events. 76 * It also adds event handlers for focus and blur events, to keep track of the focused element. 77 * Most work is done when the submit is fired, not earlier. 78 * @member NornixFormHandler 79 */ 80 function init() 81 { 82 if (!document.getElementsByTagName) return; 83 // search for messages 84 var pElement, pElements = document.getElementsByTagName("p"), i = 0; 85 while (pElement = pElements[i++]) 86 { 87 if (searchMessage.test(pElement.className)) 88 { 89 Nornix.fader(pElement, "fader", 20); 90 } 91 } 92 var form, formElements = document.getElementsByTagName("form"); 93 i = 0; 94 while (form = formElements[i++]) 95 { 96 Nornix.events.add(form, 'submit', checkForm); 97 Nornix.events.add(form, 'reset', resetForm); 98 var element, elements = form.elements, j = 0; 99 while (element = elements[j++]) 100 { 101 if (!Nornix.dom.eqNodeName(element, 'fieldset')) 102 { 103 Nornix.events.add(element, 'focus', setFocus); 104 Nornix.events.add(element, 'blur', clearFocus); 105 } 106 } 107 } 108 } 109 110 /** 111 * Main method to validate form. 112 * 113 * Runs the apropriate validators and writes error messages if needed. 114 * If there is a function assigned to the chainedSubmit property of 115 * the form, it will be called when the form validates. 116 * 117 * Form cancelling buttons: 118 * If the focused element on submit has a name ending in "-cancel", the 119 * form is sent without any validiation or other functions. 120 * 121 * @param {Event} event the event object 122 * @private 123 */ 124 var checkForm = function (event) 125 { 126 if (!this.elements) return; // nothing to work with, quit 127 if (focusedElement && focusedElement.name && searchCancel.test(focusedElement.name)) 128 { 129 return; // this is a cancel button, don't mess it up! 130 } 131 var errMsg = []; 132 var confirmSubmit = null; 133 var firstError = null; 134 var validator; 135 // store the focused element beforing using any alerts (IE!) 136 var lastFocused = focusedElement; 137 // loop form elements ("this" points to the form) 138 var e, elements = this.elements, i = 0; 139 while (e = elements[i++]) 140 { 141 var classString = e.className; 142 // loop thru validator functions 143 while (validator = searchValidate.exec(classString)) 144 { 145 var validate = thiz.validators[validator[0]]; // get validator function 146 if (validate === undefined) continue; // skip validator that doesn't exist :TODO: warning message? 147 var result = validate(e.value); 148 if (result) 149 { 150 setValid(e); 151 } 152 else 153 { 154 if (!firstError) 155 { 156 firstError = e; 157 } 158 setInvalid(e); 159 // find field label & title to use as error message 160 var msg = findLabelText(e); 161 msg += ": " + e.title; 162 errMsg.push(msg); 163 classString = ""; // no need to look further into this element (and: this is *not* a submit element!) 164 continue; 165 } 166 } 167 // submitbutton with focus and "confirm" class? 168 if (e === lastFocused && searchConfirm.test(classString)) 169 { 170 confirmSubmit = e; // save for later use 171 } 172 } 173 // check if there are errors 174 if (errMsg.length !== 0) 175 { 176 var legends = this.getElementsByTagName("legend"); 177 // we simply pick the first legend, supposing it's for the whole form 178 var heading = legends.length > 0 ? Nornix.dom.getTextContent(legends[0]) : thiz.texts.errHeading; 179 if (firstError) 180 { 181 firstError.focus(); 182 } 183 alert("=== " + heading + " ===\n\n"+errMsg.join("\n")); 184 Nornix.events.cancel(event); 185 return; 186 } 187 // check if the form submit should be confirmed 188 if (confirmSubmit) 189 { 190 // get text for confirm question 191 if (!confirmSubmit.title || confirmSubmit.title === "") 192 { 193 confirmSubmit.title = thiz.texts.confirmText; 194 } 195 // ask user to confirm 196 if (!confirm(confirmSubmit.title + '?')) 197 { 198 // don't send the form 199 Nornix.events.cancel(event); 200 return; 201 } 202 } 203 // show message if the form is OK 204 if (searchOkMessage.test(this.className)) 205 { 206 alert(thiz.texts.okForm); 207 } 208 if (thiz.settings.ieFixButtons && Nornix.util.isIe) 209 { 210 // disable not pushed submitting buttons, to make IE6 play nice 211 i = 0; 212 while (e = elements[i++]) 213 { 214 if (Nornix.dom.eqNodeName(e, 'button') && e !== lastFocused) 215 { 216 e.disabled = true; 217 } 218 } 219 } 220 // check for more functions to run before submitting the form 221 if (this.chainedSubmit) this.chainedSubmit(event); 222 }; 223 224 /** 225 * Cleanup method on form reset. 226 * Sets all elements to "valid" state. 227 * If there is a function assigned to the {@link chainedReset} property of 228 * the form, it will be called when the form is cleaned. 229 * @private 230 */ 231 var resetForm = function (event) 232 { 233 if (!this.elements) return; // nothing to work with, quit 234 var i=0, e; 235 while (e = this.elements[i++]) 236 { 237 setValid(e); 238 } 239 if (this.chainedReset) this.chainedReset(event); 240 }; 241 242 /** 243 * Find the label text for a form field. 244 * Uses [@link findLabelElement) to find the label element. 245 * @private 246 * @param {HTMLElement} el form element to find the label text for 247 * @return label text of element 248 * @type String 249 */ 250 function findLabelText (el) 251 { 252 var label = findLabelElement(el); 253 if (label) return Nornix.dom.getTextContent(label); 254 return ""; 255 } 256 257 /** 258 * Find the label element for a form field. 259 * @private 260 * @param {HTMLElement} el form element to find the label for 261 * @return label element for element or null 262 * @type HTMLLabelElement 263 */ 264 function findLabelElement (el) 265 { 266 if (el.parentNode.nodeName.toLowerCase() === 'label') 267 { 268 // el(ement) is nested inside label tag, no "for" attribute is needed 269 return el.parentNode; 270 } 271 // get id of element 272 var id = el.name ? el.name : (el.id ? el.id : null); 273 if (id === null) return null; 274 // loop thru all labels in this form 275 var labels = el.form.getElementsByTagName("label"), i=0, labl; 276 while (labl = labels[i++]) 277 { 278 // check "for" attribute of label 279 if (labl.htmlFor == id) 280 { 281 return labl; 282 } 283 } 284 return null; 285 } 286 287 /** 288 * Sets "focus memory", called as an event handler. 289 * @private 290 */ 291 function setFocus () 292 { 293 focusedElement = this; 294 } 295 296 /** 297 * Empties the "focus memory", called as an event handler. 298 * @private 299 */ 300 function clearFocus () 301 { 302 focusedElement = null; 303 } 304 305 /** 306 * Set a form field to look valid. 307 * @private 308 * @param {HTMLElement} obj form element to set to "valid" 309 */ 310 function setValid (obj) 311 { 312 setFieldState(obj, Nornix.css.remove); 313 } 314 315 /** 316 * Set a form field to look invalid. 317 * @private 318 * @param {HTMLElement} obj form element to set to "invalid" 319 */ 320 function setInvalid (obj) 321 { 322 setFieldState(obj, Nornix.css.add); 323 } 324 325 /** 326 * Set a form field and it's label to some state. 327 * @private 328 * @param {HTMLElement} obj form element to set to "valid" 329 * @param {Function} stateAction action(obj) that changes the state 330 */ 331 function setFieldState (obj, stateAction) 332 { 333 stateAction(obj, "invalid"); 334 o = findLabelElement(obj); 335 if (o) 336 { 337 stateAction(o); 338 } 339 } 340 }; 341 342 /* 343 344 FORM FIELD VALIDATOR FUNCTIONS 345 346 - the validator function gets the value of a field 347 - the functions returns true on success 348 - there can be multiple validators for a single field 349 - the validators are called by using their names as classnames on the field 350 351 */ 352 353 Nornix.FormHandler.prototype.validators = 354 { 355 /** 356 * Validate required form field. 357 * @param {Object} val form field value 358 * @return true on successful validation 359 * @type boolean 360 */ 361 validateRequired : function(val) 362 { 363 if (val == null) return false; 364 return (val.length > 0 && !/^\s+$/.test(val)); 365 }, 366 367 /** 368 * Validate a full name, that needs to have at least one given and one surname. 369 * @param {Object}} val form field value 370 * @return true on successful validation 371 * @type boolean 372 */ 373 validateFullName : function(val) 374 { 375 var regE = /^(([a-zA-ZŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ-]){1,}( |$){1}){2,4}$/; 376 return (regE.test(val)); 377 }, 378 379 /** 380 * Validate a name, only characters and hyphens are allowed. 381 * @param {Object}} val form field value 382 * @return true on successful validation 383 * @type boolean 384 */ 385 validateName : function(val) 386 { 387 var regE = /^(([a-zA-ZŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ-]){1,}( |$){1}){1,4}$/; 388 return (regE.test(val)); 389 }, 390 391 /** 392 * Validate an email address. 393 * @see http://www.quirksmode.org/js/mailcheck.html 394 * @param {Object}} val form field value 395 * @return true on successful validation 396 * @type boolean 397 */ 398 validateEmail : function(val) 399 { 400 var regE = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4}){1}$/; 401 return (regE.test(val)); 402 }, 403 404 /** 405 * Validate a number (can be any value). 406 * @param {Object}} val form field value 407 * @return true on successful validation 408 * @type boolean 409 */ 410 validateNumber : function(val) 411 { 412 return !isNaN(val); 413 }, 414 415 /** 416 * Validate a number, which has to be greater than zero. 417 * @param {Object}} val form field value 418 * @return true on successful validation 419 * @type boolean 420 */ 421 validatePositiveNumber : function(val) 422 { 423 return !isNaN(val) && val > 0; 424 }, 425 426 /** 427 * Validate an integer, which has to be greater than zero. 428 * @param {Object}} val form field value 429 * @return true on successful validation 430 * @type boolean 431 */ 432 validatePositiveInteger : function(val) 433 { 434 return !isNaN(val) && val > 0 && /^[0-9]+$/.test(val); 435 } 436 }; 437 438 /* 439 How to add a validator without making changes to this file. 440 The function should return true on successful validation. 441 You can also replace an existing function this way. 442 */ 443 Nornix.FormHandler.prototype.validators.validateABC = function(val) 444 { 445 return val === "ABC"; 446 }; 447 448 449 // settings 450 451 452 Nornix.FormHandler.prototype.texts = 453 { 454 /** 455 * Default confirm text for confirm dialogs. 456 * @type String 457 */ 458 confirmText : "Confirm action", 459 /** 460 * Default error message heading for error dialogs. 461 * @type String 462 */ 463 errHeading : "Form", 464 /** 465 * Default message for confirming that the form is beeing sent. 466 * @type String 467 */ 468 okForm : "Thank you, the form is beeing submitted!" 469 }; 470 471 Nornix.FormHandler.prototype.settings = 472 { 473 /** 474 * Set to true to remove non-pushed submit "button" elements in IE (bug fix for IE) 475 * @type boolean 476 */ 477 ieFixButtons : true 478 }; 479 480 new Nornix.FormHandler(); 481