var FormCheck = new Class({
Implements: Options,
options : {
tips_class: 'tipsbox', //tips error class
error_class: 'error_f', //div error class
display : {
showErrors : 1, //0 : onSubmit, 1 : onSubmit & onBlur
errorsLocation : 1, //1 : tips, 2 : before, 3 : after
indicateErrors : 1, //0 : none, 1 : one, 2 : all
tipsOffsetX : -30, //Left position of the tips box (margin-left)
tipsOffsetY : -5, //Top position of the tips box (margin-bottom)
tipsPosition : 'relative', //If you want to set the tips position with relative or absolute value (page not centered)
tipsContainer : 'undef', //Container of fields, to get right positions.
listErrorsAtTop : false, //list all errors at the top of the form
scrollToFirst : true, //Smooth scroll the page to first error
fadeDuration : 300 //Transition duration
},
alerts : {
required: "This field is required.",
alpha: "This field accepts alphabetic characters only.",
alphanum: "This field accepts alphanumeric characters only.",
nodigit: "No digits are accepted.",
digit: "Please enter a valid integer.",
digitmin: "The number must be at least %0 characters",
digitltd: "The value must be between %0 and %1 characters",
number: "Please enter a valid number.",
email: "Please enter a valid email.",
phone: "Please enter a valid phone.",
url: "Please enter a valid url.",
confirm: "This field is different from %0",
differs: "This value must be different of %0",
length_str: "The length is incorrect, it must be between %0 and %1 characters",
lengthmax: "The length is incorrect, it must be at max %0 characters",
lengthmin: "The length is incorrect, it must be at least %0 characters",
checkbox: "Please check the box",
radios: "Please select a radio",
select: "Please choose a value"
},
regexp : {
required : /[^.*]/,
alpha : /^[a-z ._-]+$/i,
alphanum : /^[a-z0-9 ._-]+$/i,
digit : /^[-+]?[0-9]+$/,
nodigit : /^[^0-9]+$/,
number : /^[-+]?\d*\.?\d+$/,
email : /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i,
phone : /^[\d\s ().-]+$/,
url : /^((http|https|ftp)\:\/\/)?[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&%\$#\=~])*$/i
}
},
/*
Constructor: initialize
Constructor
Add event on formular and perform some stuff, you now, like settings, ...
*/
initialize : function(form, options) {
if (this.form = $(form)) {
this.form.isValid = true;
this.regex = ['length'];
this.setOptions(options);
//internalization
if (typeof(formcheckLanguage) != 'undefined') this.options.alerts = formcheckLanguage;
this.validations = [];
this.alreadyIndicated = false;
this.firstError = false;
var regex = new Hash(this.options.regexp);
regex.each(function(el, key) {
this.regex.push(key);
}, this)
this.form.getElements("*[class*=validate]").each(function(el) {
el.validation = [];
var classes = el.getProperty("class").split(' ');
classes.each(function(classX) {
if(classX.match(/^validate(\[.+\])$/)) {
var validators = eval(classX.match(/^validate(\[.+\])$/)[1]);
for(var i = 0; i < validators.length; i++) {
el.validation.push(validators[i]);
}
this._register(el);
}
}, this);
}, this);
this.form.addEvents({
"submit": this._onSubmit.bind(this)
});
this._preloadImages();
}
},
/*
Function: _preloadImages
Private method
Preload css images
*/
_preloadImages : function() {
var images = new Array('bottom_left', 'bottom_right', 'error', 'inner', 'left', 'mark', 'right', 'side', 'top_left', 'top_right', 'top');
// we get the images folder
},
/*
Function: _register
Private method
Add listener on fields
*/
_register : function(el) {
this.validations.push(el);
el.errors = [];
if (this._isChildType(el) == false && this.options.display.showErrors == 1) el.addEvent('blur', function() {
this._manageError(el, 'blur');
}.bind(this))
//We manage errors on radio
},
/*
Function: _validate
Private method
Dispatch check to other methods
*/
_validate : function(el) {
el.errors = [];
el.isOk = true;
el.validation.each(function(rule) {
if(this._isChildType(el)) {
if (this._validateGroup(el) == false) {
el.isOk = false;
}
} else {
var ruleArgs = [];
if(rule.match(/^.+\[/)) {
var ruleMethod = rule.split('[')[0];
var ruleArgs = eval(rule.match(/^.+(\[.+\])$/)[1].replace(/([A-Z]+)/i, "'$1'"));
} else var ruleMethod = rule;
if (this.regex.contains(ruleMethod)) {
if (this._validateRegex(el, ruleMethod, ruleArgs) == false) {
el.isOk = false;
}
}
if (ruleMethod == 'confirm') {
if (this._validateConfirm(el, ruleArgs) == false) {
el.isOk = false;
}
}
if (ruleMethod == 'differs') {
if (this._validateDiffers(el, ruleArgs) == false) {
el.isOk = false;
}
}
if (el.get('tag') == "select" || el.type == "checkbox") {
if (this._simpleValidate(el) == false) {
el.isOk = false;
}
}
}
}, this);
if (el.isOk) return true;
else return false;
},
/*
Function: _simpleValidate
Private method
Perform simple check for select fields and checkboxes
*/
_simpleValidate : function(el) {
if (el.get('tag') == 'select' && (el.value == el.options[0].value)) {
el.errors.push(this.options.alerts.select);
return false;
} else if (el.type == "checkbox" && el.checked == false) {
el.errors.push(this.options.alerts.checkbox);
return false;
}
return true;
},
/*
Function: _validateRegex
Private method
Perform regex validations
*/
_validateRegex : function(el, ruleMethod, ruleArgs) {
var msg = "";
if (ruleArgs[1] && ruleMethod == 'length') {
if (ruleArgs[1] == -1) {
this.options.regexp.length = new RegExp("^.{"+ ruleArgs[0] +",}$");
msg = this.options.alerts.lengthmin.replace("%0",ruleArgs[0]);
} else {
this.options.regexp.length = new RegExp("^.{"+ ruleArgs[0] +","+ ruleArgs[1] +"}$");
msg = this.options.alerts.length_str.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
}
} else if (ruleArgs[0]) {
this.options.regexp.length = new RegExp("^.{0,"+ ruleArgs[0] +"}$");
msg = this.options.alerts.lengthmax.replace("%0",ruleArgs[0]);
} else {
msg = this.options.alerts[ruleMethod];
}
if (ruleArgs[1] && ruleMethod == 'digit') {
var regres = true;
if (!this.options.regexp.digit.test(el.value)) {
el.errors.push(this.options.alerts[ruleMethod]);
regres = false;
}
if (ruleArgs[1] == -1) {
if (el.value >= ruleArgs[0]) var valueres = true; else var valueres = false;
msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]);
} else {
if (el.value >= ruleArgs[0] && el.value <= ruleArgs[1]) var valueres = true; else var valueres = false;
msg = this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
}
if (regres == false || valueres == false) {
el.errors.push(msg);
return false;
}
} else if (this.options.regexp[ruleMethod].test(el.value) == false) {
el.errors.push(msg);
return false;
}
return true;
},
/*
Function: _validateConfirm
Private method
Perform confirm validations
*/
_validateConfirm: function(el,ruleArgs) {
if (el.validation.contains('required') == false) {
//el.validation.push('required');
}
var confirm = ruleArgs[0];
if(el.value != this.form[confirm].value){
msg = this.options.alerts.confirm.replace("%0",ruleArgs[0]);
el.errors.push(msg);
return false;
}
return true;
},
/*
Function: _validateDiffers
Private method
Perform differs validations
*/
_validateDiffers: function(el,ruleArgs) {
var confirm = ruleArgs[0];
if(el.value == this.form[confirm].value){
msg = this.options.alerts.differs.replace("%0",ruleArgs[0]);
el.errors.push(msg);
return false;
}
return true;
},
/*
Function: _isChildType
Private method
Determine if the field is a group of radio or not.
*/
_isChildType: function(el) {
var elType = el.type.toLowerCase();
if((elType == "radio")) return true;
return false;
},
/*
Function: _validateGroup
Private method
Perform radios validations
*/
_validateGroup : function(el) {
el.errors = [];
var nlButtonGroup = this.form[el.getProperty("name")];
el.group = nlButtonGroup;
var cbCheckeds = false;
for(var i = 0; i < nlButtonGroup.length; i++) {
if(nlButtonGroup[i].checked) {
cbCheckeds = true;
}
}
if(cbCheckeds == false) {
el.errors.push(this.options.alerts.radios);
return false;
} else {
return true;
}
},
/*
Function: _listErrorsAtTop
Private method
Display errors
*/
_listErrorsAtTop : function(obj) {
if(!this.form.element) {
this.form.element = new Element('div', {'id' : 'errorlist', 'class' : this.options.error_class}).injectTop(this.form);
}
if ($type(obj) == 'collection') {
new Element('p').setHTML("" + obj[0].name + " : " + obj[0].errors[0]).injectInside(this.form.element);
} else {
if ((obj.validation.contains('required') && obj.errors.length > 0) || (obj.errors.length > 0 && obj.value && obj.validation.contains('required') == false)) {
obj.errors.each(function(error) {
new Element('p').setHTML("" + obj.name + " : " + error).injectInside(this.form.element);
}, this);
}
}
},
/*
Function: _manageError
Private method
Manage display of errors boxes
*/
_manageError : function(el, method) {
var isValid = this._validate(el);
if (((!isValid && el.validation.contains('required')) || (!el.validation.contains('required') && el.value && !isValid))) {
if(this.options.display.listErrorsAtTop == true && method == 'submit')
this._listErrorsAtTop(el, method);
if (this.options.display.indicateErrors == 2 ||this.alreadyIndicated == false || el.name == this.alreadyIndicated.name)
{
this._addError(el);
return false;
}
} else if ((isValid || (!el.validation.contains('required') && !el.value)) && el.element) {
this._removeError(el);
return true;
}
return true;
},
/*
Function: _addError
Private method
Add error message
*/
_addError : function(obj) {
var IE6 = false /*@cc_on || @_jscript_version < 5.7 @*/;
this.alreadyIndicated = obj;
if(!this.firstError) this.firstError = obj;
if (IE6) {
alert(obj.errors[0])
return false;
}
if(!obj.element) {
if (this.options.display.errorsLocation == 1) {
var marginLeft = this.options.display.tipsOffsetX;
var displacement = this.form.getCoordinates().left;
oObjPos=obj.getPosition();
oObjPos['x']+=obj.getStyle('width').toInt();
var options = {
'opacity' : 0,
'position' : 'absolute',
'margin-left' : this.options.display.tipsOffsetX,
'left' : oObjPos['x'],
'bottom' : oObjPos['y']
}
obj.element = new Element('div', {'id' : 'diverror' + obj.name, 'class' : this.options.tips_class, 'styles' : options});
obj.element.injectInside(this.form);
} else if (this.options.display.errorsLocation == 2){
obj.element = new Element('div', {'id' : 'diverror' + obj.name, 'class' : this.options.error_class, 'styles' : {'opacity' : 0}});
obj.element.injectBefore(obj);
} else if (this.options.display.errorsLocation == 3){
obj.element = new Element('div', {'id' : 'diverror' + obj.name, 'class' : this.options.error_class, 'styles' : {'opacity' : 0}});
if ($type(obj.group) == 'object' || $type(obj.group) == 'collection')
obj.element.injectAfter(obj.group[obj.group.length-1]);
else
obj.element.injectAfter(obj);
}
}
if (obj.element) {
obj.element.empty();
if (this.options.display.errorsLocation == 1) {
var errors = [];
obj.errors.each(function(error) {
errors.push(new Element('p').set('html', error));
});
var tips = this._makeTips(errors).injectInside(obj.element);
$$('.tipsbox_error', tips).addEvent('click', function(){
this._removeError(obj);
}.bind(this));
obj.element.setStyle('top', obj.getCoordinates().top - tips.getCoordinates().height - this.options.display.tipsOffsetY);
} else {
obj.errors.each(function(error) {
new Element('p').setHTML(error).injectInside(obj.element);
});
}
if (!window.ie7 && obj.element.getStyle('opacity') == 0)
new Fx.Morph(obj.element, {'duration' : this.options.display.fadeDuration}).start({'opacity':[1]});
else
obj.element.setStyle('opacity', 1);
}
},
/*
Function: _removeError
Private method
Remove the error display
*/
_removeError : function(obj) {
this.firstError = false;
this.alreadyIndicated = false;
obj.errors = [];
obj.isOK = true;
if (this.options.display.errorsLocation == 2)
new Fx.Morph(obj.element, {'duration' : this.options.display.fadeDuration}).start({ 'height':[0] });
if (!window.ie7) {
new Fx.Morph(obj.element, {
'duration' : this.options.display.fadeDuration,
'onComplete' : function() {
if (obj.element) {
obj.element.dispose();
obj.element = false;
}
}.bind(this)
}).start({ 'opacity':[1,0] });
} else {
obj.element.dispose();
obj.element = false;
}
},
/*
Function: _focusOnError
Private method
Create set the focus to the first field with an error if needed
*/
_focusOnError : function (obj) {
if (this.options.display.scrollToFirst && !this.alreadyFocused && this.alreadyIndicated.element && !this.isScrolling) {
obj.focus();
this.alreadyFocused = true;
} else if (this.options.display.scrollToFirst && !this.isScrolling) {
var IE6 = false /*@cc_on || @_jscript_version < 5.7 @*/;
if (!IE6) new Fx.Scroll(window, {onComplete : function() {this.isScrolling = false;}.bind(this)}).scrollTo(0,obj.getCoordinates().top-30);
this.isScrolling = true;
obj.focus();
this.alreadyFocused = true;
}
},
/*
Function: _makeTips
Private method
Create tips boxes
*/
_makeTips : function(txt) {
var table = new Element('table', {'class' : 'tipsbox'});
table.cellPadding ='0';
table.cellSpacing ='0';
table.border ='0';
var tbody = new Element('tbody').injectInside(table);
var tr1 = new Element('tr').injectInside(tbody);
new Element('td', {'class' : 'tipsbox_top_left'}).injectInside(tr1);
new Element('td', {'class' : 'tipsbox_top'}).injectInside(tr1);
new Element('td', {'class' : 'tipsbox_top_right'}).injectInside(tr1);
var tr2 = new Element('tr').injectInside(tbody);
new Element('td', {'class' : 'tipsbox_left'}).injectInside(tr2);
var errors = new Element('td', {'class' : 'tipsbox_inner'}).injectInside(tr2);
var errorImg = new Element('div', {'class' : 'tipsbox_error'}).injectInside(errors);
txt.each(function(error) {
error.injectInside(errors);
});
new Element('td', {'class' : 'tipsbox_right'}).injectInside(tr2);
var tr3 = new Element('tr').injectInside(tbody);
new Element('td', {'class' : 'tipsbox_bottom_left'}).injectInside(tr3);
new Element('td', {'class' : 'tipsbox_mark'}).injectInside(tr3);
new Element('td', {'class' : 'tipsbox_bottom_right'}).injectInside(tr3);
return table;
},
/*
Function: _reinitialize
Private method
Reinitialize form before submit check
*/
_reinitialize: function() {
this.validations.each(function(el) {
if (el.element) {
el.errors = [];
el.isOK = true;
el.element.dispose();
el.element = false;
}
});
if (this.form.element) this.form.element.empty();
this.alreadyFocused = false;
this.firstError = false;
this.alreadyIndicated = false;
this.form.isValid = true;
},
/*
Function: _onSubmit
Private method
Perform check on submit action
*/
_onSubmit: function(event) {
this._reinitialize();
this.validations.each(function(el) {
if(!this._manageError(el,'submit')) this.form.isValid = false;
}, this);
if(!this.form.isValid) {
event.stop();
if (this.firstError) this._focusOnError(this.firstError);
return false;
}
/* Do disabled button */
for (i=event.target.length,j=0;i>j;j++) {
if (event.target[j].type == 'submit') {
event.target[j].disabled = true;
}
}
return true;
}
});
FormCheck.implement(new Options());