/**
* HTML_QuickForm2 client-side validation library
* Package version 2.0.2
* http://pear.php.net/package/HTML_QuickForm2
*
* Copyright 2006-2014, Alexey Borzov, Bertrand Mansion
* Licensed under new BSD license
* http://opensource.org/licenses/bsd-license.php
*/
/**
* @namespace Base namespace for QuickForm, we no longer define our stuff in global namespace
*/
var qf = qf || {};
/**
* @namespace Namespace for QuickForm elements' javascript
*/
qf.elements = qf.elements || {};
/**
* Enhanced version of typeof operator.
*
* Returns "null" for null values and "array" for arrays. Handles edge cases
* like objects passed across browser windows, etc. Borrowed from closure library.
*
* @param {*} value The value to get the type of
* @returns {string} Type name
*/
qf.typeOf = function(value) {
var s = typeof value;
if ('function' == s && 'undefined' == typeof value.call) {
return 'object';
} else if ('object' == s) {
if (!value) {
return 'null';
} else {
if (value instanceof Array
|| (!(value instanceof Object)
&& '[object Array]' == Object.prototype.toString.call(value)
&& 'number' == typeof value.length
&& 'undefined' != typeof value.splice
&& 'undefined' != typeof value.propertyIsEnumerable
&& !value.propertyIsEnumerable('splice'))
) {
return 'array';
}
if (!(value instanceof Object)
&& ('[object Function]' == Object.prototype.toString.call(value)
|| 'undefined' != typeof value.call
&& 'undefined' != typeof value.propertyIsEnumerable
&& !value.propertyIsEnumerable('call'))
) {
return 'function';
}
}
}
return s;
};
/**
* Builds an object structure for the provided namespace path.
*
* Ensures that names that already exist are not overwritten. For
* example:
* <code>
* "a.b.c" -> a = {};a.b={};a.b.c={};
* </code>
* Borrowed from closure library.
*
* @param {string} ns name of the object that this file defines.
*/
qf.addNamespace = function(ns) {
var parts = ns.split('.');
var cur = window;
for (var part; parts.length && (part = parts.shift());) {
if (cur[part]) {
cur = cur[part];
} else {
cur = cur[part] = {};
}
}
};
/**
* Class for Hash Map datastructure.
*
* Used for storing container values and validation errors, mostly borrowed
* from closure library.
*
* @param {Object} [inMap] Object or qf.Map instance to initialize the map
* @constructor
*/
qf.Map = function(inMap)
{
/**
* Actual JS Object used to store the map
* @type {Object}
* @private
*/
this._map = {};
/**
* An array of map keys
* @type {String[]}
* @private
*/
this._keys = [];
/**
* Number of key-value pairs in the map
* @type {number}
* @private
*/
this._count = 0;
if (inMap) {
this.merge(inMap);
}
};
qf.Map.prototype = (function(){
/**
* Wrapper function for hasOwnProperty
* @param {Object} obj
* @param {*} key
* @returns {boolean}
* @private
*/
function _hasKey(obj, key)
{
return Object.prototype.hasOwnProperty.call(obj, key);
}
/**
* Removes keys that are no longer in the map from the _keys array
* @private
*/
function _cleanupKeys()
{
if (this._count == this._keys.length) {
return;
}
var srcIndex = 0;
var destIndex = 0;
var seen = {};
while (srcIndex < this._keys.length) {
var key = this._keys[srcIndex];
if (_hasKey(this._map, key)
&& !_hasKey(seen, key)
) {
this._keys[destIndex++] = key;
seen[key] = true;
}
srcIndex++;
}
this._keys.length = destIndex;
}
return {
/**
* Whether the map has the given key
* @param {*} key
* @returns {boolean}
*/
hasKey: function(key)
{
return _hasKey(this._map, key);
},
/**
* Returns the number of key-value pairs in the Map
* @returns {number}
*/
length: function()
{
return this._count;
},
/**
* Returns the values of the Map
* @returns {Array}
*/
getValues: function()
{
_cleanupKeys.call(this);
var ret = [];
for (var i = 0; i < this._keys.length; i++) {
ret.push(this._map[this._keys[i]]);
}
return ret;
},
/**
* Returns the keys of the Map
* @returns {String[]}
*/
getKeys: function()
{
_cleanupKeys.call(this);
return (this._keys.concat());
},
/**
* Returns whether the Map is empty
* @returns {boolean}
*/
isEmpty: function()
{
return 0 == this._count;
},
/**
* Removes all key-value pairs from the map
*/
clear: function()
{
this._map = {};
this._keys.length = 0;
this._count = 0;
},
/**
* Removes a key-value pair from the Map
* @param {*} key The key to remove
* @returns {boolean} Whether the pair was removed
*/
remove: function(key)
{
if (!_hasKey(this._map, key)) {
return false;
}
delete this._map[key];
this._count--;
if (this._keys.length > this._count * 2) {
_cleanupKeys.call(this);
}
return true;
},
/**
* Returns the value for the given key
* @param {*} key The key to look for
* @param {*} [defaultVal] The value to return if the key is not in the Map
* @returns {*}
*/
get: function(key, defaultVal)
{
if (_hasKey(this._map, key)) {
return this._map[key];
}
return defaultVal;
},
/**
* Adds a key-value pair to the Map
* @param {*} key
* @param {*} value
*/
set: function(key, value)
{
if (!_hasKey(this._map, key)) {
this._count++;
this._keys.push(key);
}
this._map[key] = value;
},
/**
* Merges key-value pairs from another Object or Map
* @param {Object} map
* @param {function(*, *)} [mergeFn] Optional function to call on values if
* both maps have the same key. By default a value from the map being
* merged will be stored under that key.
*/
merge: function(map, mergeFn)
{
var keys, values, i = 0;
if (map instanceof qf.Map) {
keys = map.getKeys();
values = map.getValues();
} else {
keys = [];
values = [];
for (var key in map) {
keys[i] = key;
values[i++] = map[key];
}
}
var fn = mergeFn || qf.Map.mergeReplace;
for (i = 0; i < keys.length; i++) {
if (!this.hasKey(keys[i])) {
this.set(keys[i], values[i]);
} else {
this.set(keys[i], fn(this.get(keys[i]), values[i]));
}
}
}
};
})();
/**
* Callback for merge(), forces to use second value.
*
* This makes Map.merge() behave like PHP's array_merge() function
*
* @param {*} a Original value in map
* @param {*} b Value in the map being merged
* @returns {*} second value
*/
qf.Map.mergeReplace = function(a, b)
{
return b;
};
/**
* Callback for merge(), forces to use first value.
*
* This makes Map.merge() behave like PHP's + operator for arrays
*
* @param {*} a Original value in map
* @param {*} b Value in the map being merged
* @returns {*} first value
*/
qf.Map.mergeKeep = function(a, b)
{
return a;
};
/**
* Callback for merge(), concatenates values.
*
* If the values are not arrays, they are first converted to ones.
*
* This callback makes Map.merge() behave somewhat like PHP's array_merge_recursive()
*
* @param {*} a Original value in map
* @param {*} b Value in the map being merged
* @returns {Array} array containing both values
*/
qf.Map.mergeArrayConcat = function(a, b)
{
if ('array' != qf.typeOf(a)) {
a = [a];
}
if ('array' != qf.typeOf(b)) {
b = [b];
}
return a.concat(b);
};
/**
* @namespace Helper functions for working with form values
*/
qf.form = (function() {
/**
* Gets the value of select-multiple element.
*
* @param {Element} el The element
* @returns {String[]}
* @private
*/
function _getSelectMultipleValue(el)
{
var values = [];
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].selected) {
values.push(el.options[i].value);
}
}
return values;
}
/**
* Sets the value of a select-one element.
* @param {Element} el
* @param {String} value
* @private
*/
function _setSelectSingleValue(el, value)
{
el.selectedIndex = -1;
for (var option, i = 0; option = el.options[i]; i++) {
if (option.value == value) {
option.selected = true;
return;
}
}
}
/**
* Sets the value of a select-multiple element.
* @param {Element} el
* @param {String|String[]} value
* @private
*/
function _setSelectMultipleValue(el, value)
{
if ('array' != qf.typeOf(value)) {
value = [value];
}
for (var option, i = 0; option = el.options[i]; i++) {
option.selected = false;
for (var j = 0, l = value.length; j < l; j++) {
if (option.value == value[j]) {
option.selected = true;
}
}
}
}
return {
/**
* Gets the value of a form element.
*
* @param {string|Element} el
* @returns {string|string[]|null}
*/
getValue: function(el)
{
if (typeof el == 'string') {
el = document.getElementById(el);
}
if (!el || !('type' in el)) {
return null;
}
switch (el.type.toLowerCase()) {
case 'checkbox':
case 'radio':
return el.checked? el.value: null;
case 'select-one':
var index = el.selectedIndex;
return -1 == index? null: el.options[index].value;
case 'select-multiple':
return _getSelectMultipleValue(el);
default:
return (typeof el.value == 'undefined')? null: el.value;
}
},
/**
* Gets the submit value of a form element. It will return null for disabled
* elements and elements that cannot have submit values (buttons, reset controls).
*
* @param {string|Element} el
* @returns {string|string[]|null}
*/
getSubmitValue: function(el)
{
if (typeof el == 'string') {
el = document.getElementById(el);
}
if (!el || !('type' in el) || el.disabled) {
return null;
}
switch (el.type.toLowerCase()) {
case 'reset':
case 'button':
return null;
default:
return qf.form.getValue(el);
}
},
/**
* Gets the submit values of a container.
*
* @param [...] This accepts a variable number of arguments, that are either
* strings (considered element ID attributes), objects {name: element name,
* value: element value} or instances of qf.Map, representing the contained elements
* @returns qf.Map
*/
getContainerSubmitValue: function()
{
var k, v, map = new qf.Map();
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof qf.Map) {
map.merge(arguments[i], qf.Map.mergeArrayConcat);
} else {
if ('object' == qf.typeOf(arguments[i])) {
k = arguments[i].name;
v = arguments[i].value;
} else {
k = document.getElementById(arguments[i]).name;
v = qf.form.getSubmitValue(arguments[i]);
}
if (null !== v) {
var valueObj = {};
valueObj[k] = v;
map.merge(valueObj, qf.Map.mergeArrayConcat);
}
}
}
return map;
},
/**
* Sets the value of a form element.
* @param {String|Element} el
* @param {*} value
*/
setValue: function(el, value)
{
if (typeof el == 'string') {
el = document.getElementById(el);
}
if (!el || !('type' in el)) {
return;
}
switch (el.type.toLowerCase()) {
case 'checkbox':
case 'radio':
el.checked = !!value;
break;
case 'select-one':
_setSelectSingleValue(el, value);
break;
case 'select-multiple':
_setSelectMultipleValue(el, value);
break;
default:
el.value = value;
}
}
};
})();
/**
* Alias for qf.form.getSubmitValue
* @type {Function}
*/
qf.$v = qf.form.getSubmitValue;
/**
* Alias for qf.form.getContainerSubmitValue
* @type {Function}
*/
qf.$cv = qf.form.getContainerSubmitValue;
/**
* @namespace Functions for CSS classes handling
*/
qf.classes = {
/**
* Adds a class or a list of classes to an element, without duplicating class names
*
* @param {Node} element DOM node to add class(es) to
* @param {string|string[]} name Class name(s) to add
*/
add: function(element, name)
{
if ('string' == qf.typeOf(name)) {
name = name.split(/\\s+/);
}
if (!element.className) {
element.className = name.join(' ');
} else {
var checkName = ' ' + element.className + ' ',
newName = element.className;
for (var i = 0, len = name.length; i < len; i++) {
if (name[i] && 0 > checkName.indexOf(' ' + name[i] + ' ')) {
newName += ' ' + name[i];
}
}
element.className = newName;
}
},
/**
* Removes a class or a list of classes from an element
*
* @param {Node} element DOM node to remove class(es) from
* @param {string|string[]} name Class name(s) to remove
*/
remove: function(element, name)
{
if (!element.className) {
return;
}
if ('string' == qf.typeOf(name)) {
name = name.split(/\\s+/);
}
var className = (' ' + element.className + ' ').replace(/[\n\t\r]/g, ' ');
for (var i = 0, len = name.length; i < len; i++) {
if (name[i]) {
className = className.replace(' ' + name[i] + ' ', ' ');
}
}
element.className = className.replace(/^\s+/, '').replace(/\s+$/, '');
},
/**
* Checks whether a given element has a given class
*
* @param {Node} element DOM node to check
* @param {string} name Class name to check for
* @returns {boolean}
*/
has: function(element, name)
{
return (-1 < (' ' + element.className + ' ').replace(/[\n\t\r]/g, ' ').indexOf(' ' + name + ' '));
}
};
/**
* @namespace Minor fixes to JS events to make them a bit more crossbrowser
*/
qf.events = {
/**
* Tests for specific events support
*
* Code "inspired" by jQuery, original technique from here:
* http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
*
* @type {Object}
*/
test: (function() {
var test = {
submitBubbles: true,
changeBubbles: true,
focusinBubbles: false
};
var div = document.createElement('div');
if (div.attachEvent) {
for (var i in {'submit': true, 'change': true, 'focusin': true}) {
var eventName = 'on' + i;
var isSupported = (eventName in div);
if (!isSupported) {
div.setAttribute(eventName, 'return;');
isSupported = (typeof div[eventName] === 'function');
}
test[i + 'Bubbles'] = isSupported;
}
}
return test;
})(),
/**
* A naïve wrapper around addEventListener() and attachEvent().
*
* QuickForm does not need a complete framework for crossbrowser event handling
* and does not provide one. Use one of a zillion javascript libraries if you
* need such a framework in your application.
*
* @param {Element} element
* @param {String} type
* @param {function()} handler
* @param {boolean} [capture]
*/
addListener: function(element, type, handler, capture)
{
if (element.addEventListener) {
element.addEventListener(type, handler, capture);
} else {
element.attachEvent('on' + type, handler);
}
},
/**
* A naïve wrapper around removeEventListener() and detachEvent().
*
* @param {Element} element
* @param {String} type
* @param {function()} handler
* @param {boolean} capture
*/
removeListener: function(element, type, handler, capture)
{
if (element.removeEventListener) {
element.removeEventListener(type, handler, capture);
} else {
element.detachEvent('on' + type, handler);
}
},
/**
* Adds some standard fields to (IE's) event object.
*
* This is intended to be used in event handlers like this:
* <code>
* function handler(event) {
* event = qf.events.fixEvent(event);
* ...
* }
* </code>
*
* @param {Event} [e]
* @returns {Event}
*/
fixEvent: function(e)
{
e = e || window.event;
e.preventDefault = e.preventDefault || function() { this.returnValue = false; };
e.stopPropagation = e.stopPropagation || function() { this.cancelBubble = true; };
if (!e.target) {
e.target = e.srcElement;
}
if (!e.relatedTarget && e.fromElement) {
e.relatedTarget = e.fromElement == e.target ? e.toElement : e.fromElement;
}
if (e.pageX == null && e.clientX != null) {
var html = document.documentElement;
var body = document.body;
e.pageX = e.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0);
e.pageY = e.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0);
}
if (!e.which && e.button) {
e.which = e.button & 1 ? 1 : (e.button & 2 ? 3 : (e.button & 4 ? 2 : 0));
}
return e;
},
/**
* Attaches cross-browser "change" and "blur" handlers to form object
*
* @param {HTMLFormElement} form
* @param {function()} handler
*/
addLiveValidationHandler: function(form, handler)
{
if (this.test.changeBubbles) {
this.addListener(form, 'change', handler, true);
} else {
// Simulated bubbling change event for IE. Based on jQuery code,
// works by on-demand attaching of onchange handlers to form elements
// with a special case for checkboxes and radios
this.addListener(form, 'beforeactivate', function(event) {
var el = qf.events.fixEvent(event).target;
if (/^(?:textarea|input|select)$/i.test(el.nodeName) && !el._onchange_attached) {
if (el.type !== 'checkbox' && el.type !== 'radio') {
qf.events.addListener(el, 'change', handler);
} else {
// IE doesn't fire onchange on checkboxes and radios until blur
// so we fire a fake change onclick after "checked" property
// was changed
qf.events.addListener(el, 'propertychange', function(event) {
if (qf.events.fixEvent(event).propertyName === 'checked') {
this._checked_changed = true;
}
});
qf.events.addListener(el, 'click', function(event) {
if (this._checked_changed) {
event = qf.events.fixEvent(event);
event._type = 'change';
this._checked_changed = false;
handler(event);
}
});
}
el._onchange_attached = true;
}
});
}
if (qf.events.test.focusinBubbles) {
this.addListener(form, 'focusout', handler, true);
} else {
this.addListener(form, 'blur', handler, true);
}
}
};
/**
* Client-side rule object
*
* @param {function()} callback
* @param {string} owner
* @param {string} message
* @param {Array} chained
* @constructor
*/
qf.Rule = function(callback, owner, message, chained)
{
/**
* Function performing actual validation
* @type {function()}
*/
this.callback = callback;
/**
* ID of owner element
* @type {string}
*/
this.owner = owner;
/**
* Error message to set if validation fails
* @type {string}
*/
this.message = message;
/**
* Chained rules
* @type {Array}
*/
this.chained = chained || [[]];
};
/**
* Client-side rule object that should run onblur / onchange
*
* @param {function()} callback
* @param {string} owner
* @param {string} message
* @param {string[]} triggers
* @param {Array} chained
* @constructor
*/
qf.LiveRule = function(callback, owner, message, triggers, chained)
{
qf.Rule.call(this, callback, owner, message, chained);
/**
* IDs of elements that should trigger validation
* @type {string[]}
*/
this.triggers = triggers;
};
qf.LiveRule.prototype = new qf.Rule();
qf.LiveRule.prototype.constructor = qf.LiveRule;
/**
* Form validator, attaches handlers that run the given rules.
*
* @param {HTMLFormElement} form
* @param {qf.Rule[]} rules
* @constructor
*/
qf.Validator = function(form, rules)
{
/**
* Validation rules
* @type {qf.Rule[]}
*/
this.rules = rules || [];
/**
* Form errors, keyed by element's ID attribute
* @type {qf.Map}
*/
this.errors = new qf.Map();
form.validator = this;
qf.events.addListener(form, 'submit', qf.Validator.submitHandler);
for (var i = 0, rule; rule = this.rules[i]; i++) {
if (rule instanceof qf.LiveRule) {
qf.events.addLiveValidationHandler(form, qf.Validator.liveHandler);
break;
}
}
};
/**
* Event handler for form's onsubmit events.
* @param {Event} event
*/
qf.Validator.submitHandler = function(event)
{
event = qf.events.fixEvent(event);
var form = event.target;
if (form.validator && !form.validator.run(form)) {
event.preventDefault();
}
};
/**
* Event handler for form's onblur and onchange events
* @param {Event} event
*/
qf.Validator.liveHandler = function(event)
{
event = qf.events.fixEvent(event);
// need to check that target has a form property: http://news.php.net/php.pear.general/31445
if (event.target.form && event.target.form.validator) {
var id = event.target.id,
type = event._type || event.type,
validator = event.target.form.validator;
// Prevent duplicate validation run on blur event fired immediately after change
if ('change' === type || !validator._lastTarget || id !== validator._lastTarget) {
validator.runLive(event);
}
validator._lastTarget = id;
}
};
qf.Validator.prototype = {
/**
* CSS classes to use when marking validation status
* @type {Object}
*/
classes: {
error: 'error',
valid: 'valid',
message: 'error',
ancestor: 'element'
},
/**
* Called before starting the validation. May be used e.g. to clear the errors from form elements.
* @param {HTMLFormElement} form The form being validated currently
*/
onStart: function(form)
{
for (var i = 0, rule; rule = this.rules[i]; i++) {
this.removeRelatedErrors(rule);
}
},
/**
* Called on setting the element error
*
* @param {string} elementId ID attribute of an element
* @param {string} errorMessage
*/
onFieldError: function(elementId, errorMessage)
{
var parent = this.findAncestor(elementId);
if (!parent) {
return
}
qf.classes.add(parent, this.classes.error);
var error = document.createElement('span');
error.className = this.classes.message;
error.appendChild(document.createTextNode(errorMessage));
error.appendChild(document.createElement('br'));
if ('fieldset' != parent.nodeName.toLowerCase()) {
parent.insertBefore(error, parent.firstChild);
} else {
// error span should be inserted *after* legend, IE will render it before fieldset otherwise
var legends = parent.getElementsByTagName('legend');
if (0 == legends.length) {
parent.insertBefore(error, parent.firstChild);
} else {
legends[legends.length - 1].parentNode.insertBefore(error, legends[legends.length - 1].nextSibling);
}
}
},
/**
* Called on successfully validating the element
*
* @param {string} elementId
*/
onFieldValid: function(elementId)
{
var ancestor = this.findAncestor(elementId);
if (ancestor) {
qf.classes.add(ancestor, this.classes.valid);
}
},
/**
* Called on successfully validating the form
*/
onFormValid: function() {},
/**
* Called on failed validation
*/
onFormError: function() {},
/**
* Performs validation using the stored rules
* @param {HTMLFormElement} form The form being validated
* @returns {boolean}
*/
run: function(form)
{
this.onStart(form);
this.errors.clear();
for (var i = 0, rule; rule = this.rules[i]; i++) {
if (this.errors.hasKey(rule.owner)) {
continue;
}
this.validate(rule);
}
if (this.errors.isEmpty()) {
this.onFormValid();
return true;
} else {
this.onFormError();
return false;
}
},
/**
* Performs live validation of an element and related ones
*
* @param {Event} event Event triggering the validation
*/
runLive: function(event)
{
var testId = ' ' + event.target.id + ' ',
ruleHash = new qf.Map(),
length = -1;
// first: find all rules "related" to the given element, clear their error messages
while (ruleHash.length() > length) {
length = ruleHash.length();
for (var i = 0, rule; rule = this.rules[i]; i++) {
if (!(rule instanceof qf.LiveRule) || ruleHash.hasKey(i)) {
continue;
}
for (var j = 0, trigger; trigger = rule.triggers[j]; j++) {
if (-1 < testId.indexOf(' ' + trigger + ' ')) {
ruleHash.set(i, true);
this.removeRelatedErrors(rule);
testId += rule.triggers.join(' ') + ' ';
break;
}
}
}
}
// second: run all "related" rules
for (i = 0; rule = this.rules[i]; i++) {
if (!ruleHash.hasKey(i) || this.errors.hasKey(rule.owner)) {
continue;
}
this.validate(rule);
}
},
/**
* Performs validation, sets the element's error if validation fails.
*
* @param {qf.Rule} rule Validation rule, maybe with chained rules
* @returns {boolean}
*/
validate: function(rule)
{
var globalValid = false, localValid = rule.callback.call(this);
for (var i = 0, item; item = rule.chained[i]; i++) {
for (var j = 0, multiplier; multiplier = item[j]; j++) {
localValid = localValid && this.validate(multiplier);
if (!localValid) {
break;
}
}
globalValid = globalValid || localValid;
if (globalValid) {
break;
}
localValid = true;
}
if (!globalValid && rule.message && !this.errors.hasKey(rule.owner)) {
this.errors.set(rule.owner, rule.message);
this.onFieldError(rule.owner, rule.message);
} else if (!this.errors.hasKey(rule.owner)) {
this.onFieldValid(rule.owner);
}
return globalValid;
},
/**
* Returns the first ancestor of an element that is either a fieldset, a form or has preset CSS class
*
* @param {string} elementId
* @returns {Node}
*/
findAncestor: function(elementId)
{
var parent = document.getElementById(elementId);
// prevent setting anything on hidden elements
if (parent.type && 'hidden' === parent.type) {
return null;
}
while (!qf.classes.has(parent, this.classes.ancestor)
&& 'fieldset' != parent.nodeName.toLowerCase()
&& 'form' != parent.nodeName.toLowerCase()
) {
parent = parent.parentNode;
}
return parent;
},
/**
* Removes the error message for the given element
*
* @param {string} elementId
*/
removeErrorMessage: function(elementId)
{
var parent = this.findAncestor(elementId);
this.errors.remove(elementId);
if (parent) {
qf.classes.remove(parent, [this.classes.error, this.classes.valid]);
var spans = parent.getElementsByTagName('span');
for (var i = spans.length - 1; i >= 0; i--) {
if (qf.classes.has(spans[i], this.classes.message)) {
spans[i].parentNode.removeChild(spans[i]);
}
}
}
},
/**
* Removes error messages from owner element(s) of a given rule and chained rules
*
* @param {qf.Rule} rule
*/
removeRelatedErrors: function(rule)
{
this.removeErrorMessage(rule.owner);
for (var i = 0, item; item = rule.chained[i]; i++) {
for (var j = 0, multiplier; multiplier = item[j]; j++) {
this.removeRelatedErrors(multiplier);
}
}
}
};
/**
* @namespace Client-side implementations of Rules that are a bit too complex to inline
*/
qf.rules = qf.rules || {};
// NB: we do not overwrite qf.rules namespace because some custom rules may be already added
/**
* Returns true if all the given callbacks return true, false otherwise.
*
* Client-side implementation of HTML_QuickForm2_Rule_Each, consult PHPDoc
* description there.
*
* @param {function()[]} callbacks
* @returns {boolean}
*/
qf.rules.each = function(callbacks)
{
for (var i = 0; i < callbacks.length; i++) {
if (!callbacks[i]()) {
return false;
}
}
return true;
};
/**
* Tests that a given value is empty.
*
* A scalar value is empty if it either null, undefined or an empty string. An
* array is empty if it contains only empty values.
*
* @param {*} value
* @returns {boolean}
*/
qf.rules.empty = function(value)
{
switch (qf.typeOf(value)) {
case 'array':
for (var i = 0; i < value.length; i++) {
if (!qf.rules.empty(value[i])) {
return false;
}
}
return true;
case 'undefined':
case 'null':
return true;
default:
return '' == value;
}
};
/**
* Tests that a given value is not empty.
*
* A scalar value is not empty if it isn't either null, undefined or an empty
* string. A container is not empty if it contains at least 'minValid'
* nonempty values.
*
* @param {*} value May usually be a string, an Array or an instance of qf.Map
* @param {number} minValid Minimum number of nonempty values in Array or
* qf.Map, defaults to 1
* @returns {boolean}
*/
qf.rules.nonempty = function(value, minValid)
{
var i, valid = 0;
if ('array' == qf.typeOf(value)) {
for (i = 0; i < value.length; i++) {
if (qf.rules.nonempty(value[i], 1)) {
valid++;
}
}
return valid >= minValid;
} else if (value instanceof qf.Map) {
var values = value.getValues();
// corner case: group of checkboxes or something similar
if (1 == value.length()) {
var k = value.getKeys()[0], v = values[0];
if ('[]' == k.slice(-2) && 'array' == qf.typeOf(v)) {
return qf.rules.nonempty(v, minValid);
}
}
for (i = 0; i < values.length; i++) {
if (qf.rules.nonempty(values[i], 1)) {
valid++;
}
}
return valid >= minValid;
} else {
// in Javascript (null != '') is true!
return '' != value && 'undefined' != qf.typeOf(value) && 'null' != qf.typeOf(value);
}
};
/**
* Tests that a given value is in a commonly used email address format.
*
* @param {*} value The email address to test
* @returns {boolean}
*/
qf.rules.email = function(value)
{
if (qf.rules.empty(value)) {
return true;
}
var parts = value.split("@");
if (parts.length != 2) {
return false;
}
if (parts[0].length > 64) {
return false;
}
if (parts[1].length < 4 || parts[1].length > 255) {
return false;
}
var locals = parts[0].split(".");
for (var i = 0; i < locals.length; i++) {
if (!(/^[A-Za-z0-9!#$%&'*+\/=?^_`{|}~-]+$/.test(locals[i]))) {
return false;
}
}
return /^([a-z0-9][a-z0-9\-]*[a-z0-9]|[a-z0-9])(\.([a-z0-9][a-z0-9\-]*[a-z0-9]|[a-z0-9])){0,10}\.([a-z]{2,}){1}$/i.test(parts[1]);
};
Copyright 2K16 - 2K18 Indonesian Hacker Rulez