"use strict";
/**********************************************************************
* <p>FormManager CSS Validation provides basic functionality for
* pre-validating user input based on CSS classes set on form elements.</p>
*
* <p>The following classes can be applied to a form element for
* pre-validation:</p>
*
* <dl>
* <dt><code>yiv-required</code></dt>
* <dd>Value must not be empty.</dd>
*
* <dt><code>yiv-length:[x,y]</code></dt>
* <dd>String must be at least x characters and at most y characters.
* At least one of x and y must be specified.</dd>
*
* <dt><code>yiv-integer:[x,y]</code></dt>
* <dd>The integer value must be at least x and at most y.
* x and y are both optional.</dd>
*
* <dt><code>yiv-decimal:[x,y]</code></dt>
* <dd>The decimal value must be at least x and at most y. Exponents are
* not allowed. x and y are both optional.</dd>
* </dl>
*
* <p>If we ever need to allow exponents, we can use yiv-float.</p>
*
* @module gallery-formmgr
* @submodule gallery-formmgr-css-validation
*/
/**
* @class FormManager
*/
Y.namespace('FormManager');
// pre-validation classes
var required_class = 'yiv-required';
var length_class_re = /(?:^|\s+)yiv-length:\[([0-9]+)?,([1-9][0-9]*)?\](?:\s+|$)/;
var integer_class_re = /(?:^|\s+)yiv-integer(?::\[([-+]?[0-9]+)?,([-+]?[0-9]+)?\])?(?:\s+|$)/;
var decimal_class_re = /(?:^|\s+)yiv-decimal(?::\[([-+]?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+))?,([-+]?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+))?\])?(?:\s+|$)/;
/**
* Regular expression used to determine if a value is an integer.
* This can be localized, e.g., allow for thousands separator.
*
* @property integer_value_re
* @type {RegExp}
* @static
*/
Y.FormManager.integer_value_re = /^[-+]?[0-9]+$/;
/**
* Regular expression used to determine if a value is a decimal number.
* This can be localized, e.g., use the correct decimal separator.
*
* @property decimal_value_re
* @type {RegExp}
* @static
*/
Y.FormManager.decimal_value_re = /^[-+]?(?:[0-9]+\.?|[0-9]*\.[0-9]+)$/;
/**
* <p>Map of localizable strings used by pre-validation.</p>
*
* <dl>
* <dt>validation_error</dt>
* <dd>Displayed in `status_node` by `notifyErrors()` when pre-validation fails.</dd>
* <dt>required_string</dt>
* <dd>Displayed when `yiv-required` fails on an input field.</dd>
* <dt>required_menu</dt>
* <dd>Displayed when `yiv-required` fails on a select element.</dd>
* <dt>length_too_short, length_too_long, length_out_of_range</dt>
* <dd>Displayed when `yiv-length` fails on an input field.</dd>
* <dt>integer, integer_too_small, integer_too_large, integer_out_of_range</dt>
* <dd>Displayed when `yiv-integer` fails on an input field.</dd>
* <dt>decimal, decimal_too_small, decimal_too_large, decimal_out_of_range</dt>
* <dd>Displayed when `yiv-decimal` fails on an input field.</dd>
* </dl>
*
* @property Strings
* @type {Object}
* @static
*/
Y.FormManager.Strings =
{
validation_error: 'Correct errors in the highlighted fields before continuing.',
required_string: 'This field requires a value.',
required_menu: 'This field is required. Choose a value from the pull-down list.',
length_too_short: 'Enter text that is at least {min} characters or longer.',
length_too_long: 'Enter text that is up to {max} characters long.',
length_out_of_range: 'Enter text that is {min} to {max} characters long.',
integer: 'Enter a whole number (no decimal point).',
integer_too_small: 'Enter a number that is {min} or higher (no decimal point).',
integer_too_large: 'Enter a number that is {max} or lower (no decimal point).',
integer_out_of_range: 'Enter a number between or including {min} and {max} (no decimal point).',
decimal: 'Enter a number.',
decimal_too_small: 'Enter a number that is {min} or higher.',
decimal_too_large: 'Enter a number that is {max} or lower.',
decimal_out_of_range: 'Enter a number between or including {min} and {max}.'
};
function hasLimit(
/* string */ s)
{
return (!Y.Lang.isUndefined(s) && s.length > 0);
}
/**
* Validate an input based on its CSS data.
*
* @method validateFromCSSData
* @static
* @param e {Element|Node} The field to validate.
* @param [msg_list] {Map} Map of message types to custom messages.
* @return {Object} Status
* <dl>
* <dt>keepGoing</dt>
* <dd>(Boolean) <code>true</code> if further validation should be done.</dd>
* <dt>error</dt>
* <dd>(String) The error message, if any.</dd>
* </dl>
*/
Y.FormManager.validateFromCSSData = function(
/* element */ e,
/* map */ msg_list)
{
var Strings = Y.FormManager.Strings;
if (e._node)
{
e = e._node;
}
var required = Y.DOM.hasClass(e, required_class);
if (required && e.value === '')
{
var msg = null;
if (msg_list && msg_list.required)
{
msg = msg_list.required;
}
else if (e.tagName == 'SELECT')
{
msg = Strings.required_menu;
}
else
{
msg = Strings.required_string;
}
return { keepGoing: false, error: msg };
}
else if (!required && e.value === '')
{
return { keepGoing: false };
}
if (e.className)
{
var m = e.className.match(length_class_re);
if (m && m.length)
{
if (hasLimit(m[1]) && hasLimit(m[2]) &&
parseInt(m[1], 10) > parseInt(m[2], 10))
{
Y.error(e.name+' has min_length > max_length', null, 'FormManager');
}
var msg = null;
var has_min = (hasLimit(m[1]) && m[1] !== '0');
if (has_min && hasLimit(m[2]))
{
msg = Strings.length_out_of_range;
}
else if (has_min)
{
msg = Strings.length_too_short;
}
else if (hasLimit(m[2]))
{
msg = Strings.length_too_long;
}
if (e.value && hasLimit(m[1]) &&
e.value.length < parseInt(m[1], 10))
{
if (msg_list && msg_list.min_length)
{
msg = msg_list.min_length;
}
msg = Y.substitute(msg, {min: parseInt(m[1], 10), max: parseInt(m[2], 10)});
return { keepGoing: false, error: msg };
}
if (e.value && hasLimit(m[2]) &&
e.value.length > parseInt(m[2], 10))
{
if (msg_list && msg_list.max_length)
{
msg = msg_list.max_length;
}
msg = Y.substitute(msg, {min: parseInt(m[1], 10), max: parseInt(m[2], 10)});
return { keepGoing: false, error: msg };
}
}
var m = e.className.match(integer_class_re);
if (m && m.length)
{
if (hasLimit(m[1]) && hasLimit(m[2]) &&
parseInt(m[1], 10) > parseInt(m[2], 10))
{
Y.error(e.name+' has min_value > max_value', null, 'FormManager');
}
var value = parseInt(e.value, 10);
if (e.value &&
(!Y.FormManager.integer_value_re.test(e.value) ||
(hasLimit(m[1]) && value < parseInt(m[1], 10)) ||
(hasLimit(m[2]) && value > parseInt(m[2], 10))))
{
var msg = null;
if (msg_list && msg_list.integer)
{
msg = msg_list.integer;
}
else if (hasLimit(m[1]) && hasLimit(m[2]))
{
msg = Strings.integer_out_of_range;
}
else if (hasLimit(m[1]))
{
msg = Strings.integer_too_small;
}
else if (hasLimit(m[2]))
{
msg = Strings.integer_too_large;
}
else
{
msg = Strings.integer;
}
msg = Y.substitute(msg, {min: parseInt(m[1], 10), max: parseInt(m[2], 10)});
return { keepGoing: false, error: msg };
}
}
var m = e.className.match(decimal_class_re);
if (m && m.length)
{
if (hasLimit(m[1]) && hasLimit(m[2]) &&
parseFloat(m[1]) > parseFloat(m[2]))
{
Y.error(e.name+' has min_value > max_value', null, 'FormManager');
}
var value = parseFloat(e.value);
if (e.value &&
(!Y.FormManager.decimal_value_re.test(e.value) ||
(hasLimit(m[1]) && value < parseFloat(m[1])) ||
(hasLimit(m[2]) && value > parseFloat(m[2]))))
{
var msg = null;
if (msg_list && msg_list.decimal)
{
msg = msg_list.decimal;
}
else if (hasLimit(m[1]) &&
hasLimit(m[2]))
{
msg = Strings.decimal_out_of_range;
}
else if (hasLimit(m[1]))
{
msg = Strings.decimal_too_small;
}
else if (hasLimit(m[2]))
{
msg = Strings.decimal_too_large;
}
else
{
msg = Strings.decimal;
}
msg = Y.substitute(msg, {min: parseFloat(m[1], 10), max: parseFloat(m[2], 10)});
return { keepGoing: false, error: msg };
}
}
}
return { keepGoing: true };
};
/**
* Trim leading and trailing whitespace from the specified fields, except
* when a field has the CSS class yiv-no-trim.
*
* @method cleanValues
* @static
* @param e {Array} The fields to clean.
* @return {boolean} <code>true</code> if there are any file inputs.
*/
Y.FormManager.cleanValues = function(
/* array */ e)
{
var has_file_inputs = false;
for (var i=0; i<e.length; i++)
{
var input = e[i];
var type = input.type && input.type.toLowerCase();
if (type == 'file')
{
has_file_inputs = true;
}
else if (type == 'select-multiple')
{
// don't change the value
}
else if (input.value && !Y.DOM.hasClass(input, 'yiv-no-trim'))
{
input.value = Y.Lang.trim(input.value);
}
}
return has_file_inputs;
};
/**
* <p>Names of supported status values, highest precedence first.</p>
*
* <p>This is static because it links to CSS rules that define the
* appearance of each status type: .formmgr-has{status}</p>
*
* @property status_order
* @type {Array}
* @default [ 'error', 'warn', 'success', 'info' ]
* @static
*/
Y.FormManager.status_order =
[
'error',
'warn',
'success',
'info'
];
/**
* Get the precedence of the given status name.
*
* @method getStatusPrecedence
* @static
* @param status {String} The name of the status value.
* @return {int} The position in the <code>status_order</code> array.
*/
Y.FormManager.getStatusPrecedence = function(
/* string */ status)
{
for (var i=0; i<Y.FormManager.status_order.length; i++)
{
if (status == Y.FormManager.status_order[i])
{
return i;
}
}
return Y.FormManager.status_order.length;
};
/**
* Compare two status values.
*
* @method statusTakesPrecedence
* @static
* @param orig_status {String} The name of the original status value.
* @param new_status {String} The name of the new status value.
* @return {boolean} <code>true</code> if <code>new_status</code> takes precedence over <code>orig_status</code>
*/
Y.FormManager.statusTakesPrecedence = function(
/* string */ orig_status,
/* string */ new_status)
{
return (!orig_status || Y.FormManager.getStatusPrecedence(new_status) < Y.FormManager.getStatusPrecedence(orig_status));
};