1/* 2--- 3script: formcheck.js 4 5description: A MooTools class that allows you to perform different tests on forms to validate them before submission. 6 7authors: 8 - fyrye (http://torntech.com) 9 - weepaki 10 - floor.ch (http://mootools.floor.ch) 11 12copyright: Copyright (c) 2010-2011 13 14license: 15 - MIT License 16 17requires: 18 core/1.2.4: '*' 19 more/1.2.4.4: 20 - Fx.Scroll 21 22provides: 23 - FormCheck 24... 25*/ 26/* 27 Class: FormCheck 28 Performs different tests on forms and indicates errors. 29 30 Usage: 31 Works with these types of fields : 32 - input (text, radio, checkbox) 33 - textarea 34 - select 35 36 You just need to add a specific class to each fields you want to check. 37 For example, if you add the class 38 (code) 39 validate['required','length[4, -1]','differs:email','digit'] 40 (end code) 41 the value's field must be set (required) with a minimum length of four chars (4, -1), 42 must differs of the input named email (differs:email), and must be digit. 43 44 You can perform check during the datas entry or on the submit action, shows errors as tips or in a div before or after the field, 45 show errors one by one or all together, show a list of all errors at the top of the form, localize error messages, add new regex check, ... 46 47 The layout is design only with css. Now I added a hack to use transparent png with IE6, so you can use png images in formcheck.css (works only for theme, so the file must be named formcheck.css). It can also works with multiple forms on a single html page. 48 The class supports now internationalization. To use it, simply specify a new <script> element in your html head, like this : <script type="text/javascript" src="formcheck/lang/fr.js"></script>. 49 50 If you add the class 51 (code) 52 validate['submit'] 53 (end code) 54 to an element like an anchor (or anything else), this element will act as a submit button. 55 56 N.B. : you must load the language script before the formcheck and this method overpass the old way. You can create new languages following existing ones. You can otherwise still specifiy the alerts' strings when you initialize the Class, with options. 57 If you don't use a language script, the alert will be displayed in english. 58 59 Test type: 60 You can perform various test on fields by adding them to the validate class. Be careful to *not use space chars*. Here is the list of them. 61 62 required - The field becomes required. This is a regex, you can change it with class options. 63 alpha - The value is restricted to alphabetic chars. This is a regex, you can change it with class options. 64 alphanum - The value is restricted to alphanumeric characters only. This is a regex, you can change it with class options. 65 nodigit - The field doesn't accept digit chars. This is a regex, you can change it with class options. 66 digit - The value is restricted to digit (no floating point number) chars, you can pass two arguments (f.e. digit[21,65]) to limit the number between them. Use -1 as second argument to set no maximum. 67 number - The value is restricted to number, including floating point number. This is a regex, you can change it with class options. 68 email - The value is restricted to valid email. This is a regex, you can change it with class options. 69 image - The value is restricted to images (jpg, jpeg, png, gif, bmp). This is a regex, you can change it with class options. 70 phone - The value is restricted to phone chars. This is a regex, you can change it with class options. 71 phone_inter - The value is restricted to international phone number. This is a regex, you can change it with class options. 72 url: - The value is restricted to url. This is a regex, you can change it with class options. 73 confirm - The value has to be the same as the specified. f.e. confirm:password. 74 differs - The value has to be diferent as the one specifies. f.e. differs:user. 75 length - The value length is restricted by argument (f.e. length[6,10]). Use -1 as second argument to set no maximum. 76 group - Use to validate several checkboxes as a group. Requires 2 arguments, the second one being optional (1 by default): the group id and the minimum amount of boxes to check. The second argument may be set on any or all items of the group. See example below.
77 words - The words number is limited by arguments. f.e. words[1,13]. Use -1 as second argument to don't have a max limit. 78 target - It's not really a validation test, but it allows you to attach the error message to an other element, usefull if the input you validate is hidden. You must specifiy target id, f.e. target:myDiv. 79 80 You can also use a custom function to check a field. For example, if you have a field with class 81 (code) 82 validate['required','%customCheck'] 83 (end code) 84 the function customCheck(el) will be called to validate the field. '%customcheck' works with other validate(s) together, and '~customcheck' works if the element pass the other validate(s). 85 Here is an example of what customCheck could look : 86 (code) 87 function customCheck(el){ 88 if (!el.value.test(/^[A-Z]/)) { 89 el.errors.push("Username should begin with an uppercase letter"); 90 return false; 91 } else { 92 return true; 93 } 94 } 95 (end code) 96 97 To validate checkoxes group, you could make something like : 98 (code) 99 <input type="checkbox" name="dog" class="validate['group[1,2]']"> 100 <input type="checkbox" name="cat" class="validate['group[1]']"> 101 <input type="checkbox" name="mouse" class="validate['group[1]']"> 102 (end code) 103 For checkboxes from group 1, you will need to check at least 2 boxes. 104 105 It is now possible to register new fields after a new FormCheck call by using <FormCheck::register> (see <FormCheck::dispose> too). You need first to add the validate class to the element you want to register ( $('myInput').addClass("validate['required']") ). 106 107 Parameters: 108 When you initialize the class with addEvent, you can set some options. If you want to modify regex, you must do it in a hash, like for display or alert. You can also add new regex check method by adding the regex and an alert with the same name. 109 110 Required: 111 112 form_id - The id of the formular. This is required. 113 114 Optional: 115 116 submit - If you turn this option to false, the FormCheck will only perform a validation, without submitting the form, even on success. You can use validateSuccess event to execute some code. 117 118 submitByAjax - you can set this to true if you want to submit your form with ajax. You should use provided events to handle the ajax request (see below). By default it is false. 119 ajaxResponseDiv - id of element to inject ajax response into (can also use onAjaxSuccess). By default it is false. 120 ajaxEvalScripts - use evalScripts in the Request response. Can be true or false, by default it is false. 121 onAjaxRequest - Function to fire when the Request event starts. 122 onAjaxComplete - Function to fire when the Request event completes regardless of and prior to Success or Failure. 123 onAjaxSuccess - Function to fire when the Request receives . Args: response [the request response] - see Mootools docs for Request.onSuccess. 124 onAjaxFailure - Function to fire if the Request fails. 125 126 onSubmit - Function to fire when form is submited (so before validation) 127 onValidateSuccess - Function to fire when validation pass (you should prevent form submission with option submit:false to use this) 128 onValidateFailure - Function to fire when validation fails 129 130 tipsClass - The class to apply to tipboxes' errors. By default it is 'fc-tbx'. 131 errorClass - The class to apply to alertbox (not tips). By default it is 'fc-error'. 132 fieldErrorClass - The class to apply to fields with errors, except for radios. You should also turn on options.addClassErrorToField. By default it is 'fc-field-error' 133 134 trimValue - If set to true, strip whitespace (or other characters) from the beginning and end of values. By default it is false. 135 validateDisabled - If set to true, disabled input will be validated too, otherwise not. 136 137 Display: 138 This is a hash of display settings. in here you can modify. 139 140 showErrors - 0 : onSubmit, 1 : onSubmit & onBlur, by default it is 0. 141 titlesInsteadNames - 0 : When you do a check using differs or confirm, it takes the field name for the alert. If it's set to 1, it will use the title instead of the name. 142 errorsLocation - 1 : tips, 2 : before, 3 : after, by default it is 1. 143 indicateErrors - 0 : none, 1 : one by one, 2 : all, by default it is 1. 144 indicateErrorsInit - 0 : determine if the form must be checked on initialize. Could be usefull to force the user to update fields that don't validate. 145 keepFocusOnError - 0 : normal behaviour, 1 : the current field keep the focus as it remain errors. By default it is 0. 146 checkValueIfEmpty - 0 : When you leave a field and you have set the showErrors option to 1, the value is tested only if a value has been set. 1 : The value is tested in any case. By default it is 1. 147 addClassErrorToField - 0 : no class is added to the field, 1 : the options.fieldErrorClass is added to the field with an error (except for radio). By default it is 0. 148 removeClassErrorOnTipClosure - 0 : Error class is kept when the tip is closed, 1 : Error class is removed when the tip is closed 149 150 fixPngForIe - 0 : do nothing, 1 : fix png alpha for IE6 in formcheck.css. By default it is 1. 151 replaceTipsEffect - 0 : No effect on tips replace when we resize the broswer, 1: tween transition on browser resize; 152 closeTipsButton - 0 : the close button of the tipbox is hidden, 1 : the close button of the tipbox is visible. By default it is 1. 153 flashTips - 0 : normal behaviour, 1 : the tipbox "flash" (disappear and reappear) if errors remain when the form is submitted. By default it is 0. 154 tipsPosition - 'right' : the tips box is placed on the right part of the field, 'left' to place it on the left part. By default it is 'right'. 155 tipsOffsetX - Horizontal position of the tips box (margin-left), , by default it is 100 (px). 156 tipsOffsetY - Vertical position of the tips box (margin-bottom), , by default it is -10 (px). 157 158 listErrorsAtTop - List all errors at the top of the form, , by default it is false. 159 scrollToFirst - Smooth scroll the page to first error and focus on it, by default it is true. 160 fadeDuration - Transition duration (in ms), by default it is 300. 161 162 Alerts: 163 This is a hash of alerts settings. in here you can modify strings to localize or wathever else. %0 and %1 represent the argument. 164 165 required - "This field is required." 166 alpha - "This field accepts alphabetic characters only." 167 alphanum - "This field accepts alphanumeric characters only." 168 nodigit - "No digits are accepted." 169 digit - "Please enter a valid integer." 170 digitmin - "The number must be at least %0" 171 digitltd - "The value must be between %0 and %1" 172 number - "Please enter a valid number." 173 email - "Please enter a valid email: <br /><span>E.g. [email protected]</span>" 174 phone - "Please enter a valid phone." 175 phone_inter - "Please enter a valid international phone number." 176 url - "Please enter a valid url: <br /><span>E.g. http://www.domain.com</span>" 177 image - "This field should only contain image types"
178 confirm - "This field is different from %0" 179 differs - "This value must be different of %0" 180 length_str - "The length is incorrect, it must be between %0 and %1" 181 length_fix - "The length is incorrect, it must be exactly %0 characters" 182 lengthmax - "The length is incorrect, it must be at max %0" 183 lengthmin - "The length is incorrect, it must be at least %0" 184 words_min - "This field must concain at least %0 words, now it has %1 words" 185 words_range - "This field must contain between %0 and %1 words, now it has %2 words" 186 words_max - "This field must contain at max %0 words, now it has %1 words" 187 checkbox - "Please check the box" 188 checkboxes_group - "Please check at least %0 box(es)" 189 radios - "Please select a radio" 190 select - "Please choose a value" 191 192 Example: 193 You can initialize a formcheck (no scroll, custom classes and alert) by adding for example this in your html head this code : 194 195 (code) 196 <script type="text/javascript"> 197 window.addEvent('domready', function() { 198 var myCheck = new FormCheck('form_id', { 199 tipsClass : 'tips_box', 200 display : { 201 scrollToFirst : false 202 }, 203 alerts : { 204 required : 'This field is ablolutely required! Please enter a value' 205 } 206 }) 207 }); 208 </script> 209 (end code) 210 211 About: 212 formcheck.js v.1.6 for mootools v1.2 - 03 / 2010 213 214 by Mootools.Floor (http://mootools.floor.ch) MIT-style license 215 216 Created by Luca Pillonel (luca-at-nolocation.org), 217 Last modified by Luca Pillonel 218 219 Credits: 220 This class was inspired by fValidator by Fabio Zendhi Nagao (http://zend.lojcomm.com.br) 221 222 Thanks to all contributors from groups.google.com/group/moofloor (and others as well!) providing ideas, translations, fixes and motivation! 223*/ 224 225if(typeof $empty == 'undefined')$empty = function(){}; 226 227 228var FormCheck = new Class({ 229 230 Implements: [Options, Events], 231 232 options : { 233 234 tipsClass : 'fc-tbx', //tips error class 235 errorClass : 'fc-error', //div error class 236 fieldErrorClass : 'fc-field-error', //error class for elements 237 238 submit : true, //false : just validate the form and do nothing else. Use onValidateSuccess event to execute some code 239 submitAction: false, //Action page used to submit the form data to. 240 submitMethod: false, //Method used to submit the form, valid options : 'post' or 'get' 241 242 trimValue : false, //trim (remove whitespaces before and after) the value 243 validateDisabled : false, //skip validation on disabled input if set to false. 244 245 submitByAjax : false, //false : standard submit way, true : submit by ajax 246 ajaxResponseDiv : false, //element to inject ajax response into (can also use onAjaxSuccess) [cronix] 247 ajaxEvalScripts : false, //use evalScripts in the Request response [cronix] 248 onAjaxRequest : $empty, //Function to fire when the Request event starts 249 onAjaxComplete : $empty, //Function to fire when the Request is complete, before and regardless of Success or Failure 250 onAjaxSuccess : $empty, //Function to fire when the Request receives . Args: response [the request response] - see Mootools docs for Request.onSuccess 251 onAjaxFailure : $empty, //Function to fire if the Request fails 252 253 onSubmit : $empty, //Function to fire when user submit the form 254 onValidateSuccess : $empty, //Function to fire when validation pass 255 onValidateFailure : $empty, //Function to fire when validation fails 256 257 display : { 258 showErrors : 0, 259 titlesInsteadNames : 0, 260 errorsLocation : 1, 261 indicateErrors : 1, 262 indicateErrorsInit : 0, 263 keepFocusOnError : 0, 264 checkValueIfEmpty : 1, 265 addClassErrorToField : 0, 266 removeClassErrorOnTipClosure : 0, 267 fixPngForIe : 1, 268 replaceTipsEffect : 1, 269 flashTips : 0, 270 closeTipsButton : 1, 271 tipsPosition : "right", 272 tipsOffsetX : -45, 273 tipsOffsetY : 0, 274 listErrorsAtTop : false, 275 scrollToFirst : true, 276 fadeDuration : 300 277 }, 278 279 alerts : { 280 required : "This field is required.", 281 alpha : "This field accepts alphabetic characters only.", 282 alphanum : "This field accepts alphanumeric characters only.", 283 nodigit : "No digits are accepted.", 284 digit : "Please enter a valid integer.", 285 digitltd : "The value must be between %0 and %1", 286 number : "Please enter a valid number.", 287 email : "Please enter a valid email.", 288 image : 'This field should only contain image types', 289 phone : "Please enter a valid phone.", 290 phone_inter : "Please enter a valid international phone number.", 291 url : "Please enter a valid url.", 292 293 confirm : "This field is different from %0", 294 differs : "This value must be different of %0", 295 length_str : "The length is incorrect, it must be between %0 and %1", 296 length_fix : "The length is incorrect, it must be exactly %0 characters", 297 lengthmax : "The length is incorrect, it must be at max %0", 298 lengthmin : "The length is incorrect, it must be at least %0", 299 words_min : "This field must concain at least %0 words, currently: %1 words", 300 words_range : "This field must contain %0-%1 words, currently: %2 words", 301 words_max : "This field must contain at max %0 words, currently: %1 words", 302 checkbox : "Please check the box", 303 checkboxes_group : 'Please check at least %0 box(es)', 304 radios : "Please select a radio", 305 select : "Please choose a value", 306 select_multiple : "Please choose at least one value" 307 }, 308 309 regexp : { 310 required : /[^.*]/, 311 alpha : /^[a-z ._-]+$/i, 312 alphanum : /^[a-z0-9 ._-]+$/i, 313 digit : /^[-+]?[0-9]+$/, 314 nodigit : /^[^0-9]+$/, 315 number : /^[-+]?\d*\.?\d+$/, 316 email : /^([a-zA-Z0-9_\.\-\+%])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, 317 image : /.(jpg|jpeg|png|gif|bmp)$/i, 318 phone : /^\+{0,1}[0-9 \(\)\.\-]+$/, // alternate regex : /^[\d\s ().-]+$/,/^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/ 319 phone_inter : /^\+{0,1}[0-9 \(\)\.\-]+$/, 320 url : /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&%\$#\=~])*$/i 321 } 322 }, 323 324 /* 325 Constructor: initialize 326 Constructor 327 328 Add event on formular and perform some stuff, you now, like settings, ... 329 */ 330 initialize : function(form, options) { 331 if (this.form = $(form)) { 332 this.form.isValid = true; 333 this.regex = ['length']; 334 this.groups = {}; 335 336 //internalization 337 if (typeof(formcheckLanguage) != 'undefined') this.options.alerts = Object.merge({}, this.options.alerts, formcheckLanguage); 338 339 this.setOptions(options); 340 341 this.form.setProperty('action', 342 this.options.submitAction || this.form.getProperty('action') || 'post'); 343 344 this.form.setProperty('method', 345 this.options.submitMethod || this.form.getProperty('method') || ''); 346 347 this.validations = []; 348 this.alreadyIndicated = false;
349 this.firstError = false; 350 351 $H(this.options.regexp).each(function(el, key) { 352 this.regex.push(key); 353 }, this); 354 355 this.form.getElements("*[class*=validate]").each(function(el) { 356 this.register(el); 357 }, this); 358 359 this.form.addEvents({ 360 "submit": this.onSubmit.bind(this) 361 }); 362 363 if(this.options.display.fixPngForIe) this.fixIeStuffs(); 364 document.addEvent('mousewheel', function(){ 365 this.isScrolling = false; 366 }.bind(this)); 367 368 if (this.options.display.indicateErrorsInit) { 369 this.validations.each(function(el) { 370 if(!this.manageError(el,'submit')) this.form.isValid = false; 371 }, this); 372 } 373 } 374 }, 375 376 /* 377 Function: register 378 Allows you to declare afterward new fields to the formcheck, to check dynamically loaded fields for example. 379 By default it will be the last element to be validated as it's added after others inputs, but you can define a position with second parameter. 380 381 Example: 382 (code) 383 <script type="text/javascript"> 384 window.addEvent('domready', function() { 385 formcheck = new FormCheck('form_id'); 386 }); 387 388 // ...some code... 389 390 var newField = new Element('input', { 391 class : "validate['required']", 392 name : "new-field" 393 }).inject('form_id'); 394 formcheck.register(newField, 3); 395 396 new Element('input', { 397 class : "validate['required']", 398 name : "another-field", 399 id : "another-field" 400 }).inject('form_id'); 401 formcheck.register($('another-field')); 402 </script> 403 (end code) 404 405 See also: 406 <FormCheck::dispose> 407 */ 408 register : function(el, position) { 409 el.validation = []; 410 el.getProperty("class").split(' ').each(function(classX) { 411 if (classX.match(/^validate(\[.+\])$/)) { 412 var valid = true; 413 414 var validators = eval(classX.match(/^validate(\[.+\])$/)[1]); 415 for(var i = 0; i < validators.length; i++) { 416 el.validation.push(validators[i]); 417 if (validators[i].match(/^confirm:/)) { 418 var field = validators[i].match(/.+:(.+)$/)[1]; 419 if (this.form[field].validation.contains('required')) el.validation.push('required'); 420 } 421 if (validators[i].match(/^target:.+/)) { 422 el.target = validators[i].match(/^target:(.+)/)[1]; 423 } 424 } 425 426 //we check if group is already registered 427 el.isChild = this.isChildType(el, validators); 428 if (el.isChild && el.type == 'radio') { 429 this.validations.each(function(registeredEl){ 430 if (registeredEl.name == el.name) valid = false; 431 }, this); 432 } 433 if (el.isChild && el.type == 'checkbox') { 434 this.validations.each(function(registeredEl){ 435 if (registeredEl.groupID == el.groupID) valid = false; 436 }, this); 437 } 438 439 if (position && position <= this.validations.length) { 440 var newValidations = []; 441 this.validations.each(function(valider, i){ 442 if (position == i+1 && valid) { 443 newValidations.push(el); 444 this.addListener(el); 445 } 446 newValidations.push(valider); 447 }, this); 448 this.validations = newValidations; 449 } else if (valid) { 450 this.validations.push(el); 451 this.addListener(el); 452 } 453 } 454 }, this); 455 }, 456 457 /* 458 Function: dispose 459 Allows you to remove a declared field from formCheck 460 461 Example: 462 (code) 463 <script type="text/javascript"> 464 window.addEvent('domready', function() { 465 formcheck = new FormCheck('form_id'); 466 }); 467 468 // ...some code... 469 470 formcheck.dispose($('obsolete-field')); 471 </script> 472 (end code) 473 474 See also: 475 <FormCheck::register> 476 */ 477 dispose : function(element) { 478 this.validations.erase(element); 479 }, 480 481 /* 482 Function: addListener 483 Private method 484 485 Add listener on fields 486 */ 487 addListener : function(el) { 488 el.errors = []; 489 490 if (el.validation[0] == 'submit') { 491 el.addEvent('click', function(e){ 492 new Event(e).stop(); 493 if (this.onSubmit(e)) this.form.submit(); 494 }.bind(this)); 495 return true; 496 } 497 498 if (!el.isChild) { 499 el.addEvent('blur', function() { 500 if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'blur'); 501 }.bind(this)); 502 //We manage errors on radio 503 } else if(el.isChild && el.type == 'radio') { 504 //We get all radio from the same group and add a blur option 505 var radioGroup = this.form.getElements('input[name="'+ el.getProperty("name") +'"]'); 506 radioGroup.each(function(radio){ 507 radio.addEvent('blur', function(){ 508 if(!this.fxRunning && (el.element || this.options.display.showErrors == 1) && (this.options.display.checkValueIfEmpty || el.value)) this.manageError(el, 'click'); 509 }.bind(this)); 510 },this); 511 } 512 }, 513 514 /* 515 Function: manageError 516 Private method 517 518 Manage display of errors boxes 519 */ 520 manageError : function(el, method) { 521 var isValid = this.validate(el); 522 if (method == 'testonly') return isValid; 523 if ((!isValid && el.validation.contains('required')) || (el.value && !isValid)) { 524 if(this.options.display.listErrorsAtTop && method == 'submit') this.listErrorsAtTop(el); 525 if (this.options.display.indicateErrors == 2 ||this.alreadyIndicated == false || el == this.alreadyIndicated) { 526 if(!this.firstError) this.firstError = el; 527 this.alreadyIndicated = el; 528 529 if (this.options.display.keepFocusOnError && el == this.firstError) { 530 (function(){el.focus()}).delay(10); 531 } 532 this.addError(el); 533 return false;
534 } 535 } else if ((isValid || (!el.validation.contains('required') && !el.value))) { 536 this.removeError(el); 537 return true; 538 } 539 return true; 540 }, 541 542 /* 543 Function: validate 544 Private method 545 546 Dispatch check to other methods 547 */ 548 validate : function(el) { 549 el.errors = []; 550 el.isOk = true; 551 552 //skip validation for disabled fields and trim if specified 553 if (!this.options.validateDisabled && el.get('disabled')) return true; 554 if (this.options.trimValue && el.value) el.value = el.value.trim(); 555 556 el.validation.each(function(rule) { 557 if(el.isChild) { 558 if (!this.validateGroup(el)) el.isOk = false; 559 } else { 560 var ruleArgs = []; 561 562 if(rule.match(/target:.+/)) return; 563 var ruleMethod = rule; 564 if(rule.match(/^.+\[/)) { 565 ruleMethod = rule.split('[')[0]; 566 ruleArgs = eval(rule.match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$1'")); 567 } 568 569 if (this.regex.contains(ruleMethod) && el.get('tag') != "select") { 570 if (this.validateRegex(el, ruleMethod, ruleArgs) == false) { 571 el.isOk = false; 572 } 573 } 574 if (rule.match(/confirm:.+/)) { 575 ruleArgs = [rule.match(/.+:(.+)$/)[1]]; 576 if (this.validateConfirm(el, ruleArgs) == false) { 577 el.isOk = false; 578 } 579 } 580 if (rule.match(/differs:.+/)) { 581 ruleArgs = [rule.match(/.+:(.+)$/)[1]]; 582 if (this.validateDiffers(el, ruleArgs) == false) { 583 el.isOk = false; 584 } 585 } 586 if (ruleMethod == 'words') { 587 if (this.validateWords(el, ruleArgs) == false) { 588 el.isOk = false; 589 } 590 } 591 if (ruleMethod == 'required' && (el.get('tag') == "select" || el.type == "checkbox")) { 592 if (this.simpleValidate(el) == false) { 593 el.isOk = false; 594 } 595 } 596 if(rule.match(/%[A-Z0-9\._-]+$/i) || (el.isOk && rule.match(/~[A-Z0-9\._-]+$/i))) { 597 if(eval(rule.slice(1)+'(el)') == false) { 598 el.isOk = false; 599 } 600 } 601 } 602 }, this); 603 return ( el.isOk ) ? true : false; 604 }, 605 606 /* 607 Function: simpleValidate 608 Private method 609 610 Perform simple check for select fields and checkboxes 611 */ 612 simpleValidate : function(el) { 613 if(el.get('tag') == 'select'){ 614 if(!el.multiple) { 615 if(el.selectedIndex <= 0) { 616 el.errors.push(this.options.alerts.select); 617 return false; 618 } 619 } else { 620 var selected = false; 621 el.getChildren('option').each(function(el){ 622 if(el.selected) selected = true; 623 }); 624 625 if(!selected){ 626 el.errors.push(this.options.alerts.select_multiple); 627 return false; 628 } 629 } 630 } else if (el.type == "checkbox" && el.checked == false) { 631 el.errors.push(this.options.alerts.checkbox); 632 return false; 633 } 634 return true; 635 }, 636 637 /* 638 Function: validateRegex 639 Private method 640 641 Perform regex validations 642 */ 643 validateRegex : function(el, ruleMethod, ruleArgs) { 644 var msg = ""; 645 if (ruleMethod == 'length' && ruleArgs[1]) { 646 if (ruleArgs[1] == -1) { 647 this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +",}$"); 648 msg = this.options.alerts.lengthmin.replace("%0",ruleArgs[0]); 649 } else if(ruleArgs[0] == ruleArgs[1]) { 650 this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +"}$"); 651 msg = this.options.alerts.length_fix.replace("%0",ruleArgs[0]); 652 } else { 653 this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +","+ ruleArgs[1] +"}$"); 654 msg = this.options.alerts.length_str.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]); 655 } 656 } else if (ruleArgs[0] && ruleMethod == 'length') { 657 this.options.regexp.length = new RegExp("^.{0,"+ ruleArgs[0] +"}$"); 658 msg = this.options.alerts.lengthmax.replace("%0",ruleArgs[0]); 659 } else { 660 msg = this.options.alerts[ruleMethod]; 661 } 662 if ((ruleMethod == 'digit' || ruleMethod == 'number') && ruleArgs[1]) { 663 var valueres, regres = true; 664 if (!this.options.regexp[ruleMethod].test(el.value)) { 665 el.errors.push(this.options.alerts[ruleMethod]); 666 regres = false; 667 } 668 if (ruleArgs[1] == -1) { 669 valueres = ( el.value.toFloat() >= ruleArgs[0].toFloat() ); 670 msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]); 671 } else { 672 valueres = ( el.value.toFloat() >
672= ruleArgs[0].toFloat() && el.value.toFloat() <= ruleArgs[1].toFloat() ); 673 msg = this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]); 674 } 675 if (regres == false || valueres == false) { 676 el.errors.push(msg); 677 return false; 678 } 679 } else if (this.options.regexp[ruleMethod].test(el.value) == false) { 680 el.errors.push(msg); 681 return false; 682 } 683 return true; 684 }, 685 686 /* 687 Function: validateConfirm 688 Private method 689 690 Perform confirm validations 691 */ 692 validateConfirm: function(el,ruleArgs) { 693 var confirm = ruleArgs[0]; 694 if(el.value != this.form[confirm].value){ 695 var msg = ( this.options.display.titlesInsteadNames ) ? 696 this.options.alerts.confirm.replace("%0",this.form[confirm].getProperty('title')) : 697 this.options.alerts.confirm.replace("%0",confirm); 698 el.errors.push(msg); 699 return false; 700 } 701 return true; 702 }, 703 704 /* 705 Function: validateDiffers 706 Private method 707 708 Perform differs validations 709 */ 710 validateDiffers: function(el,ruleArgs) { 711 var differs = ruleArgs[0]; 712 if(el.value == this.form[differs].value){ 713 var msg = ( this.options.display.titlesInsteadNames ) ? 714 this.options.alerts.differs.replace("%0",this.form[differs].getProperty('title')) : 715 this.options.alerts.differs.replace("%0",differs); 716 el.errors.push(msg); 717 return false; 718 } 719 return true; 720 }, 721 722 /* 723 Function: validateWords 724 Private method 725 726 Perform word count validation 727 */ 728 validateWords: function(el,ruleArgs) { 729 var min = ruleArgs[0]; 730 var max = ruleArgs[1]; 731 732 var words = el.value.replace(/[ \t\v\n\r\f\p]/m, ' ').replace(/[,.;:]/g, ' ').clean().split(' '); 733 734 if(max == -1) { 735 if(words.length < min) { 736 el.errors.push(this.options.alerts.words_min.replace("%0", min).replace("%1", words.length)); 737 return false; 738 } 739 } else { 740 if(min > 0) { 741 if(words.length < min || words.length > max) { 742 el.errors.push(this.options.alerts.words_range.replace("%0", min).replace("%1", max).replace("%2", words.length)); 743 return false; 744 } 745 } else { 746 if(words.length > max) { 747 el.errors.push(this.options.alerts.words_max.replace("%0", max).replace("%1", words.length)); 748 return false; 749 } 750 } 751 } 752 return true; 753 }, 754 755 756 /* 757 Function: isFormValid 758 public method 759 760 Determine if the form is valid 761 762 Return true or false 763 */ 764 isFormValid: function() { 765 this.form.isValid = true; 766 this.validations.each(function(el) { 767 var validation = this.manageError(el,'testonly'); 768 if(!validation) this.form.isValid = false; 769 }, this); 770 return this.form.isValid; 771 }, 772 773 /* 774 Function: isChildType 775 Private method 776 777 Determine if the field is a group of radio, of checkboxes or not. 778 */ 779 isChildType: function(el, validators) { 780 var validator; 781 if(el.type && el.type == 'radio') { 782 return true; 783 } else if(validator = validators.join().match(/group(\[.*\])/)) { 784 var group = eval(validator[1]); 785 this.groups[group[0]] = this.groups[group[0]] || []; 786 this.groups[group[0]][0] = this.groups[group[0]][0] || []; 787 this.groups[group[0]][1] = group[1] || this.groups[group[0]][1] || 1; 788 this.groups[group[0]][0].push(el); 789 el.groupID = group[0]; 790 return true; 791 } 792 return false; 793 }, 794 795 /* 796 Function: validateGroup 797 Private method 798 799 Perform radios validations 800 */ 801 validateGroup : function(el) { 802 el.errors = []; 803 if(el.type == 'radio') { 804 var nlButtonGroup = this.form[el.getProperty("name")]; 805 el.group = nlButtonGroup; 806 var cbCheckeds = false; 807 808 for(var i = 0; i < nlButtonGroup.length; i++) { 809 if(nlButtonGroup[i].checked) { 810 cbCheckeds = true; 811 } 812 } 813 if(cbCheckeds == false) { 814 el.errors.push(this.options.alerts.radios); 815 return false; 816 } else { 817 return true; 818 } 819 // we have group of checkboxes 820 } else if(el.type == 'checkbox') { 821 //we get length of checked elements 822 var checked = 0; 823 this.groups[el.groupID][0].each(function(groupEl){ 824 if(groupEl.checked) checked++; 825 }); 826 if(checked >= this.groups[el.groupID][1]) { 827 return true; 828 } else { 829 ( this.groups[el.groupID][0].length > 1 ) ? 830 el.errors.push(this.options.alerts.checkboxes_group.replace('%0', this.groups[el.groupID][1])) : 831 el.errors.push(this.options.alerts.checkbox); 832 return false;
833 } 834 // we have unmanaged type 835 } else { 836 return false; 837 } 838 }, 839 840 /* 841 Function: listErrorsAtTop 842 Private method 843 844 Display errors 845 */ 846 listErrorsAtTop : function(obj) { 847 if(!this.form.element) { 848 this.form.element = new Element('div', {'id' : 'errorlist', 'class' : this.options.errorClass}).injectTop(this.form); 849 } 850 if ($type(obj) == 'collection') { 851 new Element('p').set('html',"<span>" + obj[0].name + " : </span>" + obj[0].errors[0]).inject(this.form.element); 852 } else { 853 if ((obj.validation.contains('required') && obj.errors.length > 0) || (obj.errors.length > 0 && obj.value && obj.validation.contains('required') == false)) { 854 obj.errors.each(function(error) { 855 new Element('p').set('html',"<span>" + obj.name + " : </span>" + error).inject(this.form.element); 856 }, this); 857 } 858 } 859 window.fireEvent('resize'); 860 }, 861 862 /* 863 Function: addError 864 Private method 865 866 Add error message 867 */ 868 addError : function(obj) { 869 //determine position 870 var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates(); 871 872 if(!obj.element && this.options.display.indicateErrors != 0) { 873 if (this.options.display.errorsLocation == 1) { 874 var pos = (this.options.display.tipsPosition == 'left') ? coord.left : coord.right; 875 var options = { 876 'opacity' : 0, 877 'position' : 'absolute', 878 'float' : 'left', 879 'left' : pos + this.options.display.tipsOffsetX 880 }; 881 obj.element = new Element('div', {'class' : this.options.tipsClass, 'styles' : options}).inject(document.body); 882 this.addPositionEvent(obj); 883 } else if (this.options.display.errorsLocation == 2){ 884 obj.element = new Element('div', {'class' : this.options.errorClass, 'styles' : {'opacity' : 0}}).injectBefore(obj); 885 } else if (this.options.display.errorsLocation == 3){ 886 obj.element = new Element('div', {'class' : this.options.errorClass, 'styles' : {'opacity' : 0}}); 887 if ($type(obj.group) == 'object' || $type(obj.group) == 'collection') 888 obj.element.injectAfter(obj.group[obj.group.length-1]); 889 else 890 obj.element.injectAfter(obj); 891 } 892 } 893 if (obj.element && obj.element != true) { 894 obj.element.empty(); 895 if (this.options.display.errorsLocation == 1) { 896 var errors = []; 897 obj.errors.each(function(error) { 898 errors.push(new Element('p').set('html', error)); 899 }); 900 var tips = this.makeTips(errors).inject(obj.element); 901 if(this.options.display.closeTipsButton) { 902 tips.getElements('a.close').addEvent('mouseup', function(){ 903 this.removeError(obj, 'tip'); 904 }.bind(this)); 905 } 906 obj.element.setStyle('top', coord.top - tips.getCoordinates().height + this.options.display.tipsOffsetY); 907 } else { 908 obj.errors.each(function(error) { 909 new Element('p').set('html',error).inject(obj.element); 910 }); 911 } 912 913 if (!this.options.display.fadeDuration || Browser.ie && Browser.version == 5 && this.options.display.errorsLocation < 2) { 914 obj.element.setStyle('opacity', 1); 915 } else { 916 obj.fx = new Fx.Tween(obj.element, { 917 'duration' : this.options.display.fadeDuration, 918 'ignore' : true, 919 'onStart' : function(){ 920 this.fxRunning = true; 921 }.bind(this), 922 'onComplete' : function() { 923 this.fxRunning = false; 924 if (obj.element && obj.element.getStyle('opacity').toInt() == 0) { 925 obj.element.destroy(); 926 obj.element = false; 927 } 928 }.bind(this) 929 }); 930 if(obj.element.getStyle('opacity').toInt() != 1) obj.fx.start('opacity', 1); 931 } 932 } 933 if (this.options.display.addClassErrorToField && !obj.isChild){ 934 obj.addClass(this.options.fieldErrorClass); 935 obj.element = obj.element || true; 936 } 937 938 }, 939 940 /* 941 Function: addPositionEvent 942 943 Update tips position after a browser resize 944 */ 945 addPositionEvent : function(obj) { 946 if(this.options.display.replaceTipsEffect) { 947 obj.event = function(){ 948 var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates(); 949 new Fx.Morph(obj.element, { 950 'duration' : this.options.display.fadeDuration 951 }).start({ 952 'left':[obj.element.getStyle('left'), coord.right + this.options.display.tipsOffsetX], 953 'top':[obj.element.getStyle('top'), coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY] 954 }); 955 }.bind(this); 956 957 } else { 958 obj.event = function(){ 959 var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates(); 960 obj.element.setStyles({ 961 'left':coord.right + this.options.display.tipsOffsetX, 962 'top':coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY 963 }); 964 }.bind(this); 965 } 966 window.addEvent('resize', obj.event); 967 }, 968 969 /* 970 Function: removeError 971 Private method 972 973 Remove the error display 974 */ 975 removeError : function(obj, method) { 976 if ((this.options.display.addClassErrorToField && !obj.isChild && this.options.display.removeClassErrorOnTipClosure) || (this.options.display.addClassErrorToField && !obj.isChild && !this.options.display.removeClassErrorOnTipClosure && method != 'tip')) 977 obj.removeClass(this.options.fieldErrorClass); 978 979 if (!obj.element) return; 980 this.alreadyIndicated = false;
981 obj.errors = []; 982 obj.isOK = true; 983 window.removeEvent('resize', obj.event); 984 if (this.options.display.errorsLocation >= 2 && obj.element) { 985 new Fx.Tween(obj.element, { 986 'duration': this.options.display.fadeDuration 987 }).start('height', 0); 988 } 989 if (!this.options.display.fadeDuration || Browser.ie && Browser.Engine.version == 5 && this.options.display.errorsLocation == 1 && obj.element) { 990 this.fxRunning = true; 991 obj.element.destroy(); 992 obj.element = false; 993 (function(){this.fxRunning = false}.bind(this)).delay(200); 994 } else if (obj.element && obj.element != true) { 995 obj.fx.start('opacity', 0); 996 } 997 }, 998 999 /* 1000 Function: focusOnError 1001 Private method 1002 1003 Create set the focus to the first field with an error if needed 1004 */ 1005 focusOnError : function (obj) { 1006 if (this.options.display.scrollToFirst && !this.alreadyFocused && !this.isScrolling) { 1007 var dest; //moved this up to stop redclariations 1008 //This can changed to a single switch using default: 1009 if (!this.options.display.indicateErrors || !this.options.display.errorsLocation) { 1010 dest = obj.getCoordinates().top-30; 1011 } else { 1012 switch (this.options.display.errorsLocation){ 1013 case 1 : 1014 dest = obj.element.getCoordinates().top; 1015 break; 1016 case 2 : 1017 dest = obj.element.getCoordinates().top-30; 1018 break; 1019 case 3 : 1020 dest = obj.getCoordinates().top-30; 1021 break; 1022 } 1023 this.isScrolling = true; 1024 } 1025 if (window.getScroll().y != dest) { 1026 new Fx.Scroll(window, { 1027 onComplete : function() { 1028 this.isScrolling = false; 1029 if (obj.getProperty('type') != 'hidden') obj.focus(); 1030 }.bind(this) 1031 }).start(0,dest); 1032 } else { 1033 this.isScrolling = false; 1034 obj.focus(); 1035 } 1036 this.alreadyFocused = true; 1037 } 1038 }, 1039 1040 /* 1041 Function: fixIeStuffs 1042 Private method 1043 1044 Fix png for IE6 1045 */ 1046 fixIeStuffs : function () { 1047 if (Browser.ie) { 1048 //We fix png stuffs 1049 var rpng = new RegExp('url\\(([\.a-zA-Z0-9_/:-]+\.png)\\)'); 1050 var search = new RegExp('(.+)formcheck\.css'); 1051 for (var i = 0; i < document.styleSheets.length; i++){ 1052 if (document.styleSheets[i].href.match(/formcheck\.css$/)) { 1053 var root = document.styleSheets[i].href.replace(search, '$1'); 1054 var count = document.styleSheets[i].rules.length; 1055 for (var j = 0; j < count; j++){ 1056 var cssstyle = document.styleSheets[i].rules[j].style; 1057 var bgimage = root + cssstyle.backgroundImage.replace(rpng, '$1'); 1058 if (bgimage && bgimage.match(/\.png/i)){ 1059 var scale = (cssstyle.backgroundRepeat == 'no-repeat') ? 'crop' : 'scale'; 1060 cssstyle.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, src=\'' + bgimage + '\', sizingMethod=\''+ scale +'\')'; 1061 cssstyle.backgroundImage = "none"; 1062 } 1063 } 1064 } 1065 } 1066 } 1067 }, 1068 1069 /* 1070 Function: makeTips 1071 Private method 1072 1073 Create tips boxes 1074 */ 1075 makeTips : function(txt) { 1076 var table = new Element('table'); 1077 table.cellPadding ='0'; 1078 table.cellSpacing ='0'; 1079 table.border ='0'; 1080 1081 var tbody = new Element('tbody').inject(table); 1082 var tr1 = new Element('tr').inject(tbody); 1083 new Element('td', {'class' : 'tl'}).inject(tr1); 1084 new Element('td', {'class' : 't'}).inject(tr1); 1085 new Element('td', {'class' : 'tr'}).inject(tr1); 1086 var tr2 = new Element('tr').inject(tbody); 1087 new Element('td', {'class' : 'l'}).inject(tr2); 1088 var cont = new Element('td', {'class' : 'c'}).inject(tr2); 1089 var errors = new Element('div', {'class' : 'err'}).inject(cont); 1090 txt.each(function(error) { 1091 error.inject(errors); 1092 }); 1093 if (this.options.display.closeTipsButton) new Element('a',{'class' : 'close'}).inject(cont); 1094 new Element('td', {'class' : 'r'}).inject(tr2); 1095 var tr3 = new Element('tr').inject(tbody); 1096 new Element('td', {'class' : 'bl'}).inject(tr3); 1097 new Element('td', {'class' : 'b'}).inject(tr3); 1098 new Element('td', {'class' : 'br'}).inject(tr3); 1099 return table; 1100 }, 1101 1102 /* 1103 Function: reinitialize 1104 Reinitialize form before submit check. You can use this also to remove all tips from a form, passing the argument "forced" ( formcheck.reinitialize('forced'); ) 1105 */ 1106 reinitialize: function(forced) { 1107 this.validations.each(function(el) { 1108 if (el.element) { 1109 el.errors = []; 1110 el.isOK = true; 1111 if(this.options.display.flashTips == 1 || forced == 'forced') { 1112 el.element.destroy(); 1113 el.element = false;
1114 } 1115 } 1116 }, this); 1117 if (this.form.element) this.form.element.empty(); 1118 this.alreadyFocused = false; 1119 this.firstError = false; 1120 this.elementToRemove = this.alreadyIndicated; 1121 this.alreadyIndicated = false; 1122 this.form.isValid = true; 1123 }, 1124 1125 /* 1126 Function: submitByAjax 1127 Private method 1128 1129 Send the form by ajax, and replace the form with response 1130 */ 1131 1132 submitByAjax: function() { 1133 this.fireEvent('ajaxRequest'); 1134 new Request({ 1135 url: this.form.action, 1136 method: this.form.method, 1137 data : this.form.toQueryString(), 1138 evalScripts: this.options.ajaxEvalScripts, 1139 onFailure: function(instance){ 1140 this.fireEvent('ajaxFailure', instance); 1141 }.bind(this), 1142 onComplete: function(instance){ 1143 this.fireEvent('ajaxComplete', instance); 1144 }.bind(this), 1145 onSuccess: function(result){ 1146 this.fireEvent('ajaxSuccess', result); 1147 if(this.options.ajaxResponseDiv) $(this.options.ajaxResponseDiv).set('html',result); 1148 }.bind(this) 1149 }).send(); 1150 return false; 1151 }, 1152 1153 /* 1154 Function: onSubmit 1155 Private method 1156 1157 Perform check on submit action 1158 */ 1159 onSubmit: function(event) { 1160 this.reinitialize(); 1161 this.fireEvent('onSubmit'); 1162 1163 this.validations.each(function(el) { 1164 var validation = this.manageError(el,'submit'); 1165 if(!validation) this.form.isValid = false; 1166 }, this); 1167 1168 if (this.form.isValid) { 1169 this.fireEvent('validateSuccess'); 1170 //moved above to allow optional settings to this.form.submit and submitByAjax to be triggered by this option 1171 return (this.options.submitByAjax)? this.submitByAjax():this.options.submit; 1172 //if this.options.submit is false it can still rely on validateSuccess event 1173 } else { 1174 if (this.elementToRemove && this.elementToRemove != this.firstError && this.options.display.indicateErrors == 1) { 1175 this.removeError(this.elementToRemove); 1176 } 1177 this.focusOnError(this.firstError); 1178 this.fireEvent('validateFailure'); 1179 return false; 1180 } 1181 } 1182});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.