fomantic-ui
Version:
Fomantic empowers designers and developers by creating a shared vocabulary for UI.
1,060 lines (970 loc) • 98.1 kB
JavaScript
/*!
* # Fomantic-UI - Form Validation
* https://github.com/fomantic/Fomantic-UI/
*
*
* Released under the MIT license
* https://opensource.org/licenses/MIT
*
*/
(function ($, window, document) {
'use strict';
function isFunction(obj) {
return typeof obj === 'function' && typeof obj.nodeType !== 'number';
}
window = window !== undefined && window.Math === Math
? window
: globalThis;
$.fn.form = function (parameters) {
var
$allModules = $(this),
$window = $(window),
time = Date.now(),
performance = [],
query = arguments[0],
methodInvoked = typeof query === 'string',
queryArguments = [].slice.call(arguments, 1),
returnedValue
;
$allModules.each(function () {
var
$module = $(this),
element = this,
formErrors = [],
keyHeldDown = false,
// set at run-time
$field,
$group,
$message,
$prompt,
$submit,
$clear,
$reset,
settings,
validation,
metadata,
selector,
className,
regExp,
error,
namespace,
moduleNamespace,
eventNamespace,
attachEventsSelector,
attachEventsAction,
submitting = false,
dirty = false,
history = ['clean', 'clean'],
instance,
module
;
module = {
initialize: function () {
// settings grabbed at run time
module.get.settings();
$module.addClass(className.initial);
if (methodInvoked) {
if (instance === undefined) {
module.instantiate();
}
module.invoke(query);
} else {
if (instance !== undefined) {
instance.invoke('destroy');
module.refresh();
}
module.verbose('Initializing form validation', $module, settings);
module.bindEvents();
module.set.defaults();
if (settings.autoCheckRequired) {
module.set.autoCheck();
}
module.instantiate();
}
},
instantiate: function () {
module.verbose('Storing instance of module', module);
instance = module;
$module
.data(moduleNamespace, module)
;
},
destroy: function () {
module.verbose('Destroying previous module', instance);
module.removeEvents();
$module
.removeData(moduleNamespace)
;
},
refresh: function () {
module.verbose('Refreshing selector cache');
$field = $module.find(selector.field);
$group = $module.find(selector.group);
$message = $module.find(selector.message);
$prompt = $module.find(selector.prompt);
$submit = $module.find(selector.submit);
$clear = $module.find(selector.clear);
$reset = $module.find(selector.reset);
},
refreshEvents: function () {
module.removeEvents();
module.bindEvents();
},
submit: function (event) {
module.verbose('Submitting form', $module);
submitting = true;
$module.trigger('submit');
if (event) {
event.preventDefault();
}
},
attachEvents: function (selector, action) {
if (!action) {
action = 'submit';
}
$(selector).on('click' + eventNamespace, function (event) {
module[action]();
event.preventDefault();
});
attachEventsSelector = selector;
attachEventsAction = action;
},
bindEvents: function () {
module.verbose('Attaching form events');
$module
.on('submit' + eventNamespace, module.validate.form)
.on('blur' + eventNamespace, selector.field, module.event.field.blur)
.on('click' + eventNamespace, selector.submit, module.submit)
.on('click' + eventNamespace, selector.reset, module.reset)
.on('click' + eventNamespace, selector.clear, module.clear)
;
$field.on('invalid' + eventNamespace, module.event.field.invalid);
if (settings.keyboardShortcuts) {
$module.on('keydown' + eventNamespace, selector.field, module.event.field.keydown);
}
$field.each(function (index, el) {
var
$input = $(el),
type = $input.prop('type'),
inputEvent = module.get.changeEvent(type, $input)
;
$input.on(inputEvent + eventNamespace, module.event.field.change);
});
// Dirty events
if (settings.preventLeaving) {
$window.on('beforeunload' + eventNamespace, module.event.beforeUnload);
}
$field.on('change' + eventNamespace
+ ' click' + eventNamespace
+ ' keyup' + eventNamespace
+ ' keydown' + eventNamespace
+ ' blur' + eventNamespace, function (e) {
module.determine.isDirty();
});
$module.on('dirty' + eventNamespace, function (e) {
settings.onDirty.call();
});
$module.on('clean' + eventNamespace, function (e) {
settings.onClean.call();
});
if (attachEventsSelector) {
module.attachEvents(attachEventsSelector, attachEventsAction);
}
},
clear: function () {
$field.each(function (index, el) {
var
$field = $(el),
$element = $field.parent(),
$fieldGroup = $field.closest($group),
$prompt = $fieldGroup.find(selector.prompt),
$calendar = $field.closest(selector.uiCalendar),
defaultValue = $field.data(metadata.defaultValue) || '',
isCheckbox = $field.is(selector.checkbox),
isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'),
isCalendar = $calendar.length > 0 && module.can.useElement('calendar'),
isErrored = $fieldGroup.hasClass(className.error)
;
if (isErrored) {
module.verbose('Resetting error on field', $fieldGroup);
$fieldGroup.removeClass(className.error);
$prompt.remove();
}
if (isDropdown) {
module.verbose('Resetting dropdown value', $element, defaultValue);
$element.dropdown('clear', true);
} else if (isCheckbox) {
$field.prop('checked', false);
} else if (isCalendar) {
$calendar.calendar('clear');
} else {
module.verbose('Resetting field value', $field, defaultValue);
$field.val('');
}
});
module.remove.states();
},
reset: function () {
$field.each(function (index, el) {
var
$field = $(el),
$element = $field.parent(),
$fieldGroup = $field.closest($group),
$calendar = $field.closest(selector.uiCalendar),
$prompt = $fieldGroup.find(selector.prompt),
defaultValue = $field.data(metadata.defaultValue),
isCheckbox = $field.is(selector.checkbox),
isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'),
isCalendar = $calendar.length > 0 && module.can.useElement('calendar'),
isFile = $field.is(selector.file),
isErrored = $fieldGroup.hasClass(className.error)
;
if (defaultValue === undefined) {
return;
}
if (isErrored) {
module.verbose('Resetting error on field', $fieldGroup);
$fieldGroup.removeClass(className.error);
$prompt.remove();
}
if (isDropdown) {
module.verbose('Resetting dropdown value', $element, defaultValue);
$element.dropdown('restore defaults', true);
} else if (isCheckbox) {
module.verbose('Resetting checkbox value', $field, defaultValue);
$field.prop('checked', defaultValue);
} else if (isCalendar) {
$calendar.calendar('set date', defaultValue);
} else {
module.verbose('Resetting field value', $field, defaultValue);
$field.val(isFile ? '' : defaultValue);
}
});
module.remove.states();
},
determine: {
isValid: function () {
var
allValid = true
;
$field.each(function (index, el) {
var $el = $(el),
validation = module.get.validation($el) || {},
identifier = module.get.identifier(validation, $el)
;
if (!module.validate.field(validation, identifier, true)) {
allValid = false;
}
});
return allValid;
},
isDirty: function (e) {
var formIsDirty = false;
$field.each(function (index, el) {
var
$el = $(el),
isCheckbox = $el.filter(selector.checkbox).length > 0,
isDirty
;
isDirty = isCheckbox
? module.is.checkboxDirty($el)
: module.is.fieldDirty($el);
$el.data(settings.metadata.isDirty, isDirty);
formIsDirty = formIsDirty || isDirty;
});
if (formIsDirty) {
module.set.dirty();
} else {
module.set.clean();
}
},
},
is: {
bracketedRule: function (rule) {
return rule.type && rule.type.match(settings.regExp.bracket);
},
// duck type rule test
shorthandRules: function (rules) {
return typeof rules === 'string' || Array.isArray(rules);
},
empty: function ($field) {
if (!$field || $field.length === 0) {
return true;
}
if ($field.is(selector.checkbox)) {
return !$field.is(':checked');
}
return module.is.blank($field);
},
blank: function ($field) {
return String($field.val()).trim() === '';
},
valid: function (field, showErrors) {
var
allValid = true
;
if (field) {
module.verbose('Checking if field is valid', field);
return module.validate.field(validation[field], field, !!showErrors);
}
module.verbose('Checking if form is valid');
$.each(validation, function (fieldName, field) {
if (!module.is.valid(fieldName, showErrors)) {
allValid = false;
}
});
return allValid;
},
dirty: function () {
return dirty;
},
clean: function () {
return !dirty;
},
fieldDirty: function ($el) {
var initialValue = $el.data(metadata.defaultValue);
// Explicitly check for undefined/null here as value may be `false`, so ($el.data(dataInitialValue) || '') would not work
if (initialValue === undefined || initialValue === null) {
initialValue = '';
} else if (Array.isArray(initialValue)) {
initialValue = initialValue.toString();
}
var currentValue = $el.val();
if (currentValue === undefined || currentValue === null) {
currentValue = '';
} else if (Array.isArray(currentValue)) {
// multiple select values are returned as arrays which are never equal, so do string conversion first
currentValue = currentValue.toString();
}
// Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison
var boolRegex = /^(true|false)$/i;
var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue);
if (isBoolValue) {
var regex = new RegExp('^' + initialValue + '$', 'i');
return !regex.test(currentValue);
}
return currentValue !== initialValue;
},
checkboxDirty: function ($el) {
var initialValue = $el.data(metadata.defaultValue);
var currentValue = $el.is(':checked');
return initialValue !== currentValue;
},
justDirty: function () {
return history[0] === 'dirty';
},
justClean: function () {
return history[0] === 'clean';
},
},
removeEvents: function () {
$module.off(eventNamespace);
$field.off(eventNamespace);
$submit.off(eventNamespace);
if (settings.preventLeaving) {
$window.off(eventNamespace);
}
if (attachEventsSelector) {
$(attachEventsSelector).off(eventNamespace);
attachEventsSelector = undefined;
}
},
event: {
field: {
keydown: function (event) {
var
$field = $(this),
key = event.which,
isInput = $field.is(selector.input),
isCheckbox = $field.is(selector.checkbox),
isInDropdown = $field.closest(selector.uiDropdown).length > 0,
keyCode = {
enter: 13,
escape: 27,
}
;
if (key === keyCode.escape) {
module.verbose('Escape key pressed blurring field');
$field[0]
.blur()
;
}
if (!event.ctrlKey && key === keyCode.enter && isInput && !isInDropdown && !isCheckbox) {
if (!keyHeldDown) {
$field.one('keyup' + eventNamespace, module.event.field.keyup);
module.submit(event);
module.debug('Enter pressed on input submitting form');
}
keyHeldDown = true;
}
},
keyup: function () {
keyHeldDown = false;
},
invalid: function (event) {
event.preventDefault();
},
blur: function (event) {
var
$field = $(this),
validationRules = module.get.validation($field) || {},
identifier = module.get.identifier(validationRules, $field)
;
if (settings.on === 'blur' || (!$module.hasClass(className.initial) && settings.revalidate)) {
module.debug('Revalidating field', $field, validationRules);
module.validate.field(validationRules, identifier);
if (!settings.inline) {
module.validate.form(false, true);
}
}
},
change: function (event) {
var
$field = $(this),
validationRules = module.get.validation($field) || {},
identifier = module.get.identifier(validationRules, $field)
;
if (settings.on === 'change' || (!$module.hasClass(className.initial) && settings.revalidate)) {
clearTimeout(module.timer);
module.timer = setTimeout(function () {
module.debug('Revalidating field', $field, validationRules);
module.validate.field(validationRules, identifier);
if (!settings.inline) {
module.validate.form(false, true);
}
}, settings.delay);
}
},
},
beforeUnload: function (event) {
if (module.is.dirty() && !submitting) {
event = event || window.event;
// For modern browsers
if (event) {
event.returnValue = settings.text.leavingMessage;
}
// For olders...
return settings.text.leavingMessage;
}
},
},
get: {
ancillaryValue: function (rule) {
if (!rule.type || (!rule.value && !module.is.bracketedRule(rule))) {
return false;
}
return rule.value !== undefined
? rule.value
: rule.type.match(settings.regExp.bracket)[1] + '';
},
ruleName: function (rule) {
if (module.is.bracketedRule(rule)) {
return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], '');
}
return rule.type;
},
changeEvent: function (type, $input) {
return ['file', 'checkbox', 'radio', 'hidden'].indexOf(type) >= 0 || $input.is('select') ? 'change' : 'input';
},
fieldsFromShorthand: function (fields) {
var
fullFields = {}
;
$.each(fields, function (name, rules) {
if (!Array.isArray(rules) && typeof rules === 'object') {
fullFields[name] = rules;
} else {
if (typeof rules === 'string') {
rules = [rules];
}
fullFields[name] = {
rules: [],
};
$.each(rules, function (index, rule) {
fullFields[name].rules.push({ type: rule });
});
}
});
return fullFields;
},
identifier: function (validation, $el) {
return validation.identifier || $el.attr('id') || $el.attr('name') || $el.data(metadata.validate);
},
prompt: function (rule, field) {
var
ruleName = module.get.ruleName(rule),
ancillary = module.get.ancillaryValue(rule),
$field = module.get.field(field.identifier),
value = $field.val(),
prompt = isFunction(rule.prompt)
? rule.prompt(value)
: rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule,
requiresValue = prompt.search('{value}') !== -1,
requiresName = prompt.search('{name}') !== -1,
parts,
suffixPrompt
;
if (ancillary && ['integer', 'decimal', 'number', 'size'].indexOf(ruleName) >= 0 && ancillary.indexOf('..') >= 0) {
parts = ancillary.split('..', 2);
if (!rule.prompt && ruleName !== 'size') {
suffixPrompt = parts[0] === ''
? settings.prompt.maxValue.replace(/{ruleValue}/g, '{max}')
: (parts[1] === ''
? settings.prompt.minValue.replace(/{ruleValue}/g, '{min}')
: settings.prompt.range);
prompt += suffixPrompt.replace(/{name}/g, ' ' + settings.text.and);
}
prompt = prompt.replace(/{min}/g, parts[0]);
prompt = prompt.replace(/{max}/g, parts[1]);
}
if (ancillary && ['match', 'different'].indexOf(ruleName) >= 0) {
prompt = prompt.replace(/{ruleValue}/g, module.get.fieldLabel(ancillary, true));
}
if (requiresValue) {
prompt = prompt.replace(/{value}/g, $field.val());
}
if (requiresName) {
prompt = prompt.replace(/{name}/g, module.get.fieldLabel($field));
}
prompt = prompt.replace(/{identifier}/g, field.identifier);
prompt = prompt.replace(/{ruleValue}/g, ancillary);
if (!rule.prompt) {
module.verbose('Using default validation prompt for type', prompt, ruleName);
}
return prompt;
},
settings: function () {
if ($.isPlainObject(parameters)) {
if (parameters.fields) {
parameters.fields = module.get.fieldsFromShorthand(parameters.fields);
}
settings = $.extend(true, {}, $.fn.form.settings, parameters);
validation = $.extend(true, {}, $.fn.form.settings.defaults, settings.fields);
module.verbose('Extending settings', validation, settings);
} else {
settings = $.extend(true, {}, $.fn.form.settings);
validation = $.extend(true, {}, $.fn.form.settings.defaults);
module.verbose('Using default form validation', validation, settings);
}
// shorthand
namespace = settings.namespace;
metadata = settings.metadata;
selector = settings.selector;
className = settings.className;
regExp = settings.regExp;
error = settings.error;
moduleNamespace = 'module-' + namespace;
eventNamespace = '.' + namespace;
// grab instance
instance = $module.data(moduleNamespace);
// refresh selector cache
(instance || module).refresh();
},
field: function (identifier, strict) {
module.verbose('Finding field with identifier', identifier);
identifier = module.escape.string(identifier);
var t;
t = $field.filter('#' + identifier);
if (t.length > 0) {
return t;
}
t = $field.filter('[name="' + identifier + '"]');
if (t.length > 0) {
return t;
}
t = $field.filter('[name="' + identifier + '[]"]');
if (t.length > 0) {
return t;
}
t = $field.filter('[data-' + metadata.validate + '="' + identifier + '"]');
if (t.length > 0) {
return t;
}
module.error(error.noField.replace('{identifier}', identifier));
return strict ? $() : $('<input/>');
},
fields: function (fields, strict) {
var
$fields = $()
;
$.each(fields, function (index, name) {
$fields = $fields.add(module.get.field(name, strict));
});
return $fields;
},
fieldLabel: function (identifier, useIdAsFallback) {
var $field = typeof identifier === 'string'
? module.get.field(identifier)
: identifier,
$label = $field.closest(selector.group).find('label:not(:empty)').eq(0)
;
return $label.length === 1
? $label.text()
: $field.prop('placeholder') || (useIdAsFallback ? identifier : settings.text.unspecifiedField);
},
validation: function ($field) {
var
fieldValidation,
identifier
;
if (!validation) {
return false;
}
$.each(validation, function (fieldName, field) {
identifier = field.identifier || fieldName;
$.each(module.get.field(identifier), function (index, groupField) {
if (groupField == $field[0]) {
field.identifier = identifier;
fieldValidation = field;
return false;
}
});
});
return fieldValidation || false;
},
value: function (field, strict) {
var
fields = [],
results,
resultKeys
;
fields.push(field);
results = module.get.values.call(element, fields, strict);
resultKeys = Object.keys(results);
return resultKeys.length > 0 ? results[resultKeys[0]] : undefined;
},
values: function (fields, strict) {
var
$fields = Array.isArray(fields) && fields.length > 0
? module.get.fields(fields, strict)
: $field,
values = {}
;
$fields.each(function (index, field) {
var
$field = $(field),
$calendar = $field.closest(selector.uiCalendar),
name = $field.prop('name'),
value = $field.val(),
isCheckbox = $field.is(selector.checkbox),
isRadio = $field.is(selector.radio),
isMultiple = name.indexOf('[]') !== -1,
isCalendar = $calendar.length > 0 && module.can.useElement('calendar'),
isChecked = isCheckbox
? $field.is(':checked')
: false
;
if (name) {
if (isMultiple) {
name = name.replace('[]', '');
if (!values[name]) {
values[name] = [];
}
if (isCheckbox) {
if (isChecked) {
values[name].push(value || true);
} else {
values[name].push(false);
}
} else {
values[name].push(value);
}
} else {
if (isRadio) {
if (values[name] === undefined || values[name] === false) {
values[name] = isChecked
? value || true
: false;
}
} else if (isCheckbox) {
values[name] = isChecked ? value || true : false;
} else if (isCalendar) {
var date = $calendar.calendar('get date');
if (date !== null) {
switch (settings.dateHandling) {
case 'date': {
values[name] = date;
break;
}
case 'input': {
values[name] = $calendar.calendar('get input date');
break;
}
case 'formatter': {
var type = $calendar.calendar('setting', 'type');
switch (type) {
case 'date': {
values[name] = settings.formatter.date(date);
break;
}
case 'datetime': {
values[name] = settings.formatter.datetime(date);
break;
}
case 'time': {
values[name] = settings.formatter.time(date);
break;
}
case 'month': {
values[name] = settings.formatter.month(date);
break;
}
case 'year': {
values[name] = settings.formatter.year(date);
break;
}
default: {
module.debug('Wrong calendar mode', $calendar, type);
values[name] = '';
}
}
break;
}
}
} else {
values[name] = '';
}
} else {
values[name] = value;
}
}
}
});
return values;
},
dirtyFields: function () {
return $field.filter(function (index, e) {
return $(e).data(metadata.isDirty);
});
},
},
has: {
field: function (identifier) {
module.verbose('Checking for existence of a field with identifier', identifier);
return module.get.field(identifier, true).length > 0;
},
},
can: {
useElement: function (element) {
if ($.fn[element] !== undefined) {
return true;
}
module.error(error.noElement.replace('{element}', element));
return false;
},
},
escape: {
string: function (text) {
text = String(text);
return text.replace(regExp.escape, '\\$&');
},
},
checkErrors: function (errors, internal) {
if (!errors || errors.length === 0) {
if (!internal) {
module.error(settings.error.noErrorMessage);
}
return false;
}
if (!internal) {
errors = typeof errors === 'string'
? [errors]
: errors;
}
return errors;
},
add: {
// alias
rule: function (name, rules) {
module.add.field(name, rules);
},
field: function (name, rules) {
// Validation should have at least a standard format
if (validation[name] === undefined || validation[name].rules === undefined) {
validation[name] = {
rules: [],
};
}
var
newValidation = {
rules: [],
}
;
if (module.is.shorthandRules(rules)) {
rules = Array.isArray(rules)
? rules
: [rules];
$.each(rules, function (_index, rule) {
newValidation.rules.push({ type: rule });
});
} else {
newValidation.rules = rules.rules;
}
// For each new rule, check if there's not already one with the same type
$.each(newValidation.rules, function (_index, rule) {
if ($.grep(validation[name].rules, function (item) {
return item.type === rule.type;
}).length === 0) {
validation[name].rules.push(rule);
}
});
module.debug('Adding rules', newValidation.rules, validation);
module.refreshEvents();
},
fields: function (fields) {
validation = $.extend(true, {}, validation, module.get.fieldsFromShorthand(fields));
module.refreshEvents();
},
prompt: function (identifier, errors, internal) {
errors = module.checkErrors(errors);
if (errors === false) {
return;
}
var
$field = module.get.field(identifier),
$fieldGroup = $field.closest($group),
$prompt = $fieldGroup.children(selector.prompt),
promptExists = $prompt.length > 0,
canTransition = settings.transition && module.can.useElement('transition')
;
module.verbose('Adding field error state', identifier);
if (!internal) {
$fieldGroup
.addClass(className.error)
;
}
if (settings.inline) {
if (promptExists) {
if (canTransition) {
if ($prompt.transition('is animating')) {
$prompt.transition('stop all');
}
} else if ($prompt.is(':animated')) {
$prompt.stop(true, true);
}
$prompt = $fieldGroup.children(selector.prompt);
promptExists = $prompt.length > 0;
}
if (!promptExists) {
$prompt = $('<div/>').addClass(className.label);
if (!canTransition) {
$prompt.css('display', 'none');
}
$prompt
.appendTo($fieldGroup)
;
}
$prompt
.html(settings.templates.prompt(errors))
;
if (!promptExists) {
if (canTransition) {
module.verbose('Displaying error with css transition', settings.transition);
$prompt.transition(settings.transition + ' in', settings.duration);
} else {
module.verbose('Displaying error with fallback javascript animation');
$prompt
.fadeIn(settings.duration)
;
}
}
} else {
module.verbose('Inline errors are disabled, no inline error added', identifier);
}
},
errors: function (errors) {
errors = module.checkErrors(errors);
if (errors === false) {
return;
}
module.debug('Adding form error messages', errors);
module.set.error();
var customErrors = [],
tempErrors
;
if ($.isPlainObject(errors)) {
$.each(Object.keys(errors), function (i, id) {
if (module.checkErrors(errors[id], true) !== false) {
if (settings.inline) {
module.add.prompt(id, errors[id]);
} else {
tempErrors = module.checkErrors(errors[id]);
if (tempErrors !== false) {
$.each(tempErrors, function (index, tempError) {
customErrors.push(settings.prompt.addErrors
.replace(/{name}/g, module.get.fieldLabel(id))
.replace(/{error}/g, tempError));
});
}
}
}
});
} else {
customErrors = errors;
}
if (customErrors.length > 0) {
$message
.html(settings.templates.error(customErrors))
;
}
},
},
remove: {
errors: function () {
module.debug('Removing form error messages');
$message.empty();
},
states: function () {
$module.removeClass(className.error).removeClass(className.success).addClass(className.initial);
if (!settings.inline) {
module.remove.errors();
}
module.determine.isDirty();
},
rule: function (field, rule) {
var
rules = Array.isArray(rule)
? rule
: [rule]
;
if (validation[field] === undefined || !Array.isArray(validation[field].rules)) {
return;
}
if (rule === undefined) {
module.debug('Removed all rules');
if (module.has.field(field)) {
validation[field].rules = [];
} else {
delete validation[field];
}
return;
}
$.each(validation[field].rules, function (index, rule) {
if (rule && rules.indexOf(rule.type) !== -1) {
module.debug('Removed rule', rule.type);
validation[field].rules.splice(index, 1);
}
});
},
field: function (field) {
var
fields = Array.isArray(field)
? field
: [field]
;
$.each(fields, function (index, field) {
module.remove.rule(field);
});
module.refreshEvents();
},
// alias
rules: function (field, rules) {
if (Array.isArray(field)) {
$.each(field, function (index, field) {
module.remove.rule(field, rules);
});
} else {
module.remove.rule(field, rules);
}
},
fields: function (fields) {