Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Converted rails.validations.js to CoffeeScript

  • Loading branch information...
commit 8ba8f3f0674a112a61db270a0152654403712974 1 parent 03e48e8
Brian Cardarella bcardarella authored
1  client_side_validations.gemspec
View
@@ -24,4 +24,5 @@ Gem::Specification.new do |s|
s.add_development_dependency 'shotgun'
s.add_development_dependency 'thin'
s.add_development_dependency 'json'
+ s.add_development_dependency 'coffee-script'
end
673 coffeescript/rails.validations.coffee
View
@@ -7,370 +7,317 @@
http://www.opensource.org/licenses/mit-license.php
###
-(function ($) {
- $.fn.validate = function () {
- return this.filter('form[data-validate]').each(function () {
- var form = $(this),
- settings = window['ClientSideValidations']['forms'][form.attr('id')],
- addError = function (element, message) {
- ClientSideValidations.formBuilders[settings.type].add(element, settings, message);
- },
- removeError = function (element) {
- ClientSideValidations.formBuilders[settings.type].remove(element, settings);
- };
-
- // Set up the events for the form
- form
- .submit( function () { return form.isValid(settings.validators); })
- .bind('ajax:beforeSend', function (eventData) { if(eventData.target == this) return form.isValid(settings.validators); })
- // Callbacks
- .bind('form:validate:after', function (eventData) { ClientSideValidations.callbacks.form.after( form, eventData); } )
- .bind('form:validate:before', function (eventData) { ClientSideValidations.callbacks.form.before(form, eventData); } )
- .bind('form:validate:fail', function (eventData) { ClientSideValidations.callbacks.form.fail( form, eventData); } )
- .bind('form:validate:pass', function (eventData) { ClientSideValidations.callbacks.form.pass( form, eventData); } )
-
- // Set up the events for each validatable form element
- .find('[data-validate="true"]:input:enabled:not(:radio)')
- .live('focusout', function () { $(this).isValid(settings.validators); })
- .live('change', function () { $(this).data('changed', true); })
- // Callbacks
- .live('element:validate:after', function (eventData) { ClientSideValidations.callbacks.element.after( $(this), eventData); })
- .live('element:validate:before', function (eventData) { ClientSideValidations.callbacks.element.before($(this), eventData); })
- .live('element:validate:fail', function (eventData, message) {
- var element = $(this);
- ClientSideValidations.callbacks.element.fail(element, message, function () {
- addError(element, message);
- }, eventData); })
- .live('element:validate:pass', function (eventData) {
- var element = $(this);
- ClientSideValidations.callbacks.element.pass(element, function () {
- removeError(element);
- }, eventData); })
- // Checkboxes - Live events don't support filter
- .end().find('[data-validate="true"]:checkbox')
- .live('click', function () { $(this).isValid(settings.validators); })
- // Inputs for confirmations
- .end().find('[id*=_confirmation]').each(function () {
- var confirmationElement = $(this),
- element = form.find('#' + this.id.match(/(.+)_confirmation/)[1] + '[data-validate="true"]:input');
-
- if (element[0]) {
- $('#' + confirmationElement.attr('id'))
- .live('focusout', function () {
- element.data('changed', true).isValid(settings.validators);
- })
- .live('keyup', function () {
- element.data('changed', true).isValid(settings.validators);
- });
- }
- });
-
- });
- };
-
- $.fn.isValid = function (validators) {
- if ($(this[0]).is('form')) {
- return validateForm($(this[0]), validators);
- } else {
- return validateElement($(this[0]), validators[this[0].name]);
- }
- };
-
- var validateForm = function (form, validators) {
- var valid = true;
-
- form.trigger('form:validate:before').find('[data-validate="true"]:input:enabled').each(function() {
- if (!$(this).isValid(validators)) { valid = false; }
- });
-
- if (valid) {
- form.trigger('form:validate:pass');
- } else {
- form.trigger('form:validate:fail');
- }
-
- form.trigger('form:validate:after');
- return valid;
- },
- validateElement = function (element, validators) {
- element.trigger('element:validate:before');
-
- if (element.data('changed') !== false) {
- var valid = true;
- element.data('changed', false);
-
- // Because 'length' is defined on the list of validators we cannot call jQuery.each on
- for (kind in ClientSideValidations.validators.local) {
- if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
- element.trigger('element:validate:fail', message).data('valid', false);
- valid = false;
- break;
- }
- }
-
- if (valid) {
- for (kind in ClientSideValidations.validators.remote) {
- if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
- element.trigger('element:validate:fail', message).data('valid', false);
- valid = false;
- break;
- }
- }
- }
-
- if (valid) { element.data('valid', null); element.trigger('element:validate:pass'); }
- }
-
- element.trigger('element:validate:after');
- return element.data('valid') === false ? false : true;
- };
-
- // Main hook
- // If new forms are dynamically introduced into the DOM the .validate() method
- // must be invoked on that form
- $(function () { $('form[data-validate]').validate(); });
-})(jQuery);
-
-var ClientSideValidations = {
- forms: {},
- validators: {
- all: function() { return jQuery.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote); },
- local: {
- presence: function (element, options) {
- if (/^\s*$/.test(element.val() || "")) {
- return options.message;
- }
- },
- acceptance: function (element, options) {
- switch (element.attr('type')) {
- case 'checkbox':
- if (!element.attr('checked')) {
- return options.message;
- }
- break;
- case 'text':
- if (element.val() != (options.accept || '1')) {
- return options.message;
- }
- break;
- }
- },
- format: function (element, options) {
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- if (options['with'] && !options['with'].test(element.val())) {
- return options.message;
- } else if (options['without'] && options['without'].test(element.val())) {
- return options.message;
- }
- }
- },
- numericality: function (element, options) {
- if (!/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/.test(element.val())) {
- return options.messages.numericality;
- }
-
- if (options.only_integer && !/^[+-]?\d+$/.test(element.val())) {
- return options.messages.only_integer;
- }
-
- var CHECKS = { greater_than: '>', greater_than_or_equal_to: '>=',
- equal_to: '==', less_than: '<', less_than_or_equal_to: '<=' };
-
- for (check in CHECKS) {
- if (options[check] !== undefined && !(new Function("return " + element.val() + CHECKS[check] + options[check])())) {
- return options.messages[check];
- }
- }
-
- if (options.odd && !(parseInt(element.val(), 10) % 2)) {
- return options.messages.odd;
- }
-
- if (options.even && (parseInt(element.val(), 10) % 2)) {
- return options.messages.even;
- }
- },
- length: function (element, options) {
- var blankOptions = {},
- CHECKS = { is: '==', minimum: '>=', maximum: '<=' },
- tokenizer = options.js_tokenizer || "split('')",
- tokenized_length = new Function("element", "return (element.val()." + tokenizer + " || '').length;")(element);
- if (options.is) {
- blankOptions.message = options.messages.is;
- } else if (options.minimum) {
- blankOptions.message = options.messages.minimum;
- }
- if ((message = this.presence(element, blankOptions)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- for (check in CHECKS) {
- if (options[check] && !(new Function("return " + tokenized_length + CHECKS[check] + options[check])())) {
- return options.messages[check];
- }
- }
- }
- },
- exclusion: function (element, options) {
- var lower = null, upper = null;
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- if (options['in']) {
- for (i = 0; i < options['in'].length; i = i + 1) {
- if (options['in'][i] == element.val()) {
- return options.message;
- }
- }
- } else if (options.range) {
- lower = options.range[0];
- upper = options.range[1];
- if (element.val() >= lower && element.val() <= upper) {
- return options.message;
- }
- }
- }
- },
- inclusion: function (element, options) {
- var lower = null, upper = null;
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
+$ = jQuery
+$.fn.validate = ->
+ return @.filter('form[data-validate]').each ->
+ form = $(@)
+ settings = window['ClientSideValidations']['forms'][form.attr('id')]
+ addError = (element, message) ->
+ ClientSideValidations.formBuilders[settings.type].add(element, settings, message)
+ removeError = (element) ->
+ ClientSideValidations.formBuilders[settings.type].remove(element, settings)
+
+ # Set up the events for the form
+ form
+ .submit(-> form.isValid(settings.validators))
+ .bind('ajax:beforeSend', (eventData) -> return form.isValid(settings.validators) if eventData.target == @)
+ # Callbacks
+ .bind('form:validate:after', (eventData) -> ClientSideValidations.callbacks.form.after( form, eventData))
+ .bind('form:validate:before', (eventData) -> ClientSideValidations.callbacks.form.before(form, eventData))
+ .bind('form:validate:fail', (eventData) -> ClientSideValidations.callbacks.form.fail( form, eventData))
+ .bind('form:validate:pass', (eventData) -> ClientSideValidations.callbacks.form.pass( form, eventData))
+
+ # Set up events for each validatable form element
+ .find('[data-validate="true"]:input:enabled:not(:radio)')
+ .live('focusout', -> $(@).isValid(settings.validators))
+ .live('change', -> $(@).data('changed', true))
+ # Callbacks
+ .live('element:validate:after', (eventData) -> ClientSideValidations.callbacks.element.after( $(@), eventData))
+ .live('element:validate:before', (eventData) -> ClientSideValidations.callbacks.element.before($(@), eventData))
+ .live('element:validate:fail', (eventData, message) ->
+ element = $(@)
+ ClientSideValidations.callbacks.element.fail(element, message, ->
+ addError(element, message)
+ , eventData))
+ .live('element:validate:pass', (eventData, message) ->
+ element = $(@)
+ ClientSideValidations.callbacks.element.pass(element, ->
+ removeError(element)
+ , eventData))
+ # Checkboxes - Live events don't support filter
+ .end().find('[data-validate="true"]:checkbox').live('click', -> $(@).isValid(settings.validators))
+ .end().find('[id*=_confirmation]').each ->
+ confirmationElement = $(@)
+ element = form.find("##{@.id.match(/(.+)_confirmation/)[1]}[data-validate='true']:input")
+ if element[0]
+ $("##{confirmationElement.attr('id')}")
+ .live('focusout', ->
+ element.data('changed', true).isValid(settings.validators)
+ ).live('keyup', ->
+ element.data('changed', true).isValid(settings.validators)
+ )
+
+$.fn.isValid = (validators) ->
+ if $(@[0]).is('form')
+ return validateForm($(@[0]), validators)
+ else
+ return validateElement($(@[0]), validators[@[0].name])
+
+validateForm = (form, validators) ->
+ valid = true
+ form.trigger('form:validate:before').find('[data-validate="true"]:input:enabled').each ->
+ if !$(@).isValid(validators)
+ valid = false
+
+ if valid
+ form.trigger('form:validate:pass')
+ else
+ form.trigger('form:validate:fail')
+
+ form.trigger('form:validate:after')
+ return valid
+
+validateElement = (element, validators) ->
+ element.trigger('element:validate:before')
+
+ if element.data('changed') != false
+ valid = true
+ element.data('changed', false)
+
+ # Because 'length' is defined on the list of validators we cannot call jQuery.each
+ for kind of ClientSideValidations.validators.local
+ if validators[kind] and (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))
+ element.trigger('element:validate:fail', message).data('valid', false)
+ valid = false
+ break
+
+ if valid
+ for kind of ClientSideValidations.validators.remote
+ if validators[kind] and (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))
+ element.trigger('element:validate:fail', message).data('valid', false)
+ valid = false
+ break
+
+ if valid
+ element.data('valid', null)
+ element.trigger('element:validate:pass')
+
+ element.trigger('element:validate:after')
+ return if element.data('valid') == false then false else true
+
+# Main Hook
+# If new forms are dynamically introduced into the DOM the .validate() function
+# must be invoked on that form
+$ -> $('form[data-validate]').validate()
+
+window.ClientSideValidations =
+ forms: {}
+ validators:
+ all: -> return $.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote)
+ local:
+ presence: (element, options) ->
+ if (/^\s*$/.test(element.val() || ""))
+ return options.message
+
+ acceptance: (element, options) ->
+ switch element.attr('type')
+ when 'checkbox'
+ if !element.attr('checked')
+ return options.message
+ when 'text'
+ if element.val() != (options.accept || '1').toString()
+ return options.message
+
+ format: (element, options) ->
+ if (message = @.presence(element, options)) and options.allow_blank == true
return;
- } else if (message) {
- return message;
- } else {
- if (options['in']) {
- for (i = 0; i < options['in'].length; i = i + 1) {
- if (options['in'][i] == element.val()) {
- return;
- }
- }
+ else if message
+ return message
+ else
+ if options['with'] and !options['with'].test(element.val())
+ return options.message
+
+ numericality: (element, options) ->
+ if !/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/.test(element.val())
+ return options.messages.numericality
+
+ if options.only_integer && !/^[+-]?\d+$/.test(element.val())
+ return options.messages.only_integer
+
+ CHECKS =
+ greater_than: '>'
+ greater_than_or_equal_to: '>='
+ equal_to: '=='
+ less_than: '<'
+ less_than_or_equal_to: '<='
+
+ for check of CHECKS
+ if options[check] != undefined and !(new Function("return " + element.val() + CHECKS[check] + options[check])())
+ return options.messages[check]
+
+ if options.odd && !(parseInt(element.val(), 10) % 2)
+ return options.messages.odd
+
+ if options.even && (parseInt(element.val(), 10) % 2)
+ return options.messages.even
+
+ length: (element, options) ->
+ blankOptions = {}
+ CHECKS =
+ is: '=='
+ minimum: '>='
+ maximum: '<='
+ tokenizer = options.js_tokenizer || "split('')"
+ tokenized_length = new Function("element", "return (element.val().#{tokenizer} || '').length;")(element)
+
+ if options.is
+ blankOptions.message = options.messages.is
+ else if options.minimum
+ blankOptions.message = options.messages.minimum
+
+ if (message = this.presence(element, blankOptions)) and options.allow_blank == true
+ return
+ else if message
+ return message
+ else
+ for check of CHECKS
+ if options[check] and !(new Function("return " + tokenized_length + CHECKS[check] + options[check])())
+ return options.messages[check]
+
+ exclusion: (element, options) ->
+ lower = null
+ upper = null
+
+ if (message = this.presence(element, options)) and options.allow_blank == true
+ return
+ else if message
+ return message
+ else
+ if options['in']
+ for value of options['in']
+ if value.toString() == element.val()
+ return options.message
+ else if options.range
+ lower = options.range[0]
+ upper = options.range[1]
+
+ if element.val() >= lower && element.val() <= upper
+ return options.message
+
+ inclusion: (element, options) ->
+ lower = null
+ upper = null
+
+ if (message = this.presence(element, options)) and options.allow_blank == true
+ return
+ else if message
+ return message
+ else
+ if options['in']
+ for value of options['in']
+ if value.toString() == element.val()
+ return
return options.message;
- } else if (options.range) {
- lower = options.range[0];
- upper = options.range[1];
-
- if (element.val() >= lower && element.val() <= upper) {
- return;
- } else {
- return options.message;
- }
- }
- }
- },
- confirmation: function (element, options) {
- if (element.val() !== jQuery('#' + element.attr('id') + '_confirmation').val()) {
- return options.message;
- }
- }
- },
- remote: {
- uniqueness: function (element, options) {
- if ((message = ClientSideValidations.validators.local.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- var data = {},
- name = null;
- data.case_sensitive = !!options.case_sensitive;
- if (options.id) {
- data.id = options.id;
- }
-
- if (options.scope) {
- data.scope = {};
- for (key in options.scope) {
- var scoped_element = jQuery('[name="' + element.attr('name').replace(/\[\w+\]$/, '[' + key + ']' + '"]'));
- if (scoped_element[0] && scoped_element.val() !== options.scope[key]) {
- data.scope[key] = scoped_element.val();
- scoped_element.unbind('change.' + element.id).bind('change.' + element.id, function() { element.trigger('change'); element.trigger('focusout'); });
- } else {
- data.scope[key] = options.scope[key];
- }
- }
- }
-
- // Kind of a hack but this will isolate the resource name and attribute.
- // e.g. user[records_attributes][0][title] => records[title]
- // e.g. user[record_attributes][title] => record[title]
- // Server side handles classifying the resource properly
- if (/_attributes\]/.test(element.attr('name'))) {
- name = element.attr('name').match(/\[\w+_attributes\]/g).pop().match(/\[(\w+)_attributes\]/).pop();
- name += /(\[\w+\])$/.exec(element.attr('name'))[1];
- } else {
- name = element.attr('name');
- }
-
- // Override the name if a nested module class is passed
- if (options['class']) {
- name = options['class'] + '[' + name.split('[')[1];
- }
- data[name] = element.val();
-
- if (jQuery.ajax({
- url: '/validators/uniqueness',
- data: data,
- async: false
- }).status === 200) {
+ else if options.range
+ lower = options.range[0]
+ upper = options.range[1]
+
+ if element.val() >= lower && element.val() <= upper
+ return
+ else
+ return options.message
+
+ confirmation: (element, options) ->
+ if element.val() != $("##{element.attr('id')}_confirmation").val()
+ return options.message
+
+ remote:
+ uniqueness: (element, options) ->
+ if (message = ClientSideValidations.validators.local.presence(element, options)) and options.allow_blank == true
+ return
+ else if message
+ return message
+ else
+ data = {}
+ name = null
+ data.case_sensitive = !!options.case_sensitive
+
+ if options.id
+ data.id = options.id
+
+ if options.scope
+ data.scope = {}
+ for key of options.scope
+ scoped_element = $('[name="' + element.attr('name').replace(/\[\w+\]$/, '[' + key + ']' + '"]'))
+ # scoped_element = $('[name="' + element.attr('name').replace(/\[\w+\]$/, '[' + key + ']' + '"]'))
+ if scoped_element[0] && scoped_element.val() != options.scope[key]
+ data.scope[key] = scoped_element.val()
+ scoped_element.unbind("change.#{element.id}").bind("change.#{element.id}", ->
+ element.trigger('change')
+ element.trigger('focusout')
+ )
+ else
+ data.scope[key] = options.scope[key]
+
+ # Kind of a hack but this will isolate the resource name and attribute.
+ # e.g. user[records_attributes][0][title] => records[title]
+ # e.g. user[record_attributes][title] => record[title]
+ # Server side handles classifying the resource properly
+ if /_attributes\]/.test(element.attr('name'))
+ name = element.attr('name').match(/\[\w+_attributes\]/g).pop().match(/\[(\w+)_attributes\]/).pop()
+ name += /(\[\w+\])$/.exec(element.attr('name'))[1]
+ else
+ name = element.attr('name')
+
+ # Override the name if a nested module class is passed
+ if options['class']
+ name = "#{options['class']}[#{name.split('[')[1]}"
+
+ data[name] = element.val()
+
+ if $.ajax({url: '/validators/uniqueness', data: data, async: false}).status == 200
return options.message;
- }
- }
- }
- }
- },
- formBuilders: {
- 'ActionView::Helpers::FormBuilder': {
- add: function (element, settings, message) {
- if (element.data('valid') !== false && jQuery('label.message[for="' + element.attr('id') + '"]')[0] === undefined) {
- var inputErrorField = jQuery(settings.input_tag),
- labelErrorField = jQuery(settings.label_tag),
- label = jQuery('label[for="' + element.attr('id') + '"]:not(.message)');
-
- if (element.attr('autofocus')) { element.attr('autofocus', false); }
- element.before(inputErrorField);
- inputErrorField.find('span#input_tag').replaceWith(element);
- inputErrorField.find('label.message').attr('for', element.attr('id'));
- labelErrorField.find('label.message').attr('for', element.attr('id'));
- label.replaceWith(labelErrorField);
- labelErrorField.find('label#label_tag').replaceWith(label);
- }
- jQuery('label.message[for="' + element.attr('id') + '"]').text(message);
- },
- remove: function (element, settings) {
- var errorFieldClass = jQuery(settings.input_tag).attr('class'),
- inputErrorField = element.closest('.' + errorFieldClass),
- label = jQuery('label[for="' + element.attr('id') + '"]:not(.message)'),
- labelErrorField = label.closest('.' + errorFieldClass);
-
- if (inputErrorField[0]) {
- inputErrorField.find('#' + element.attr('id')).detach();
- inputErrorField.replaceWith(element);
- label.detach();
- labelErrorField.replaceWith(label);
- }
- }
- },
- },
- callbacks: {
- element: {
- after: function (element, eventData) { },
- before: function (element, eventData) { },
- fail: function (element, message, addError, eventData) { addError(); },
- pass: function (element, removeError, eventData) { removeError(); }
- },
- form: {
- after: function (form, eventData) { },
- before: function (form, eventData) { },
- fail: function (form, eventData) { },
- pass: function (form, eventData) { }
- }
- }
-};
+ formBuilders:
+ 'ActionView::Helpers::FormBuilder':
+ add: (element, settings, message) ->
+ if element.data('valid') != false and $("label.message[for='#{element.attr('id')}']")[0] == undefined
+ inputErrorField = $(settings.input_tag)
+ labelErrorField = $(settings.label_tag)
+ label = $("label[for='#{element.attr('id')}']:not(.message)")
+
+ if element.attr('autofocus')
+ element.attr('autofocus', false)
+
+ element.before(inputErrorField)
+ inputErrorField.find('span#input_tag').replaceWith(element)
+ inputErrorField.find('label.message').attr('for', element.attr('id'))
+ labelErrorField.find('label.message').attr('for', element.attr('id'))
+ label.replaceWith(labelErrorField)
+ labelErrorField.find('label#label_tag').replaceWith(label)
+
+ $("label.message[for='#{element.attr('id')}']").text(message);
+
+ remove: (element, settings) ->
+ errorFieldClass = $(settings.input_tag).attr('class')
+ inputErrorField = element.closest(".#{errorFieldClass}")
+ label = $("label[for='#{element.attr('id')}']:not(.message)")
+ labelErrorField = label.closest(".#{errorFieldClass}")
+
+ if inputErrorField[0]
+ inputErrorField.find("##{element.attr('id')}").detach()
+ inputErrorField.replaceWith(element)
+ label.detach()
+ labelErrorField.replaceWith(label)
+
+ callbacks:
+ element:
+ after: (element, eventData) ->
+ before: (element, eventData) ->
+ fail: (element, message, addError, eventData) -> addError()
+ pass: (element, removeError, eventData) -> removeError()
+ form:
+ after: (form, eventData) ->
+ before: (form, eventData) ->
+ fail: (form, eventData) ->
+ pass: (form, eventData) ->
1  gemfiles/Gemfile.rails-3.2.x
View
@@ -2,6 +2,7 @@ source 'http://rubygems.org'
gem 'rails', '~> 3.2.0'
gem 'mocha'
+gem 'coffee-script'
if RUBY_VERSION < '1.9'
gem 'minitest'
616 vendor/assets/javascripts/rails.validations.js
View
@@ -1,74 +1,79 @@
-/*!
- * Rails 3 Client Side Validations - v3.2.0.beta.1
- * https://github.com/bcardarella/client_side_validations
- *
- * Copyright (c) 2011 Brian Cardarella
- * Licensed under the MIT license
- * http://www.opensource.org/licenses/mit-license.php
- */
-(function ($) {
- $.fn.validate = function () {
- return this.filter('form[data-validate]').each(function () {
- var form = $(this),
- settings = window['ClientSideValidations']['forms'][form.attr('id')],
- addError = function (element, message) {
- ClientSideValidations.formBuilders[settings.type].add(element, settings, message);
- },
- removeError = function (element) {
- ClientSideValidations.formBuilders[settings.type].remove(element, settings);
- };
+/*
+ Rails 3 Client Side Validations - v3.2.0.beta.1
+ https://github.com/bcardarella/client_side_validations
- // Set up the events for the form
- form
- .submit( function () { return form.isValid(settings.validators); })
- .bind('ajax:beforeSend', function (eventData) { if(eventData.target == this) return form.isValid(settings.validators); })
- // Callbacks
- .bind('form:validate:after', function (eventData) { ClientSideValidations.callbacks.form.after( form, eventData); } )
- .bind('form:validate:before', function (eventData) { ClientSideValidations.callbacks.form.before(form, eventData); } )
- .bind('form:validate:fail', function (eventData) { ClientSideValidations.callbacks.form.fail( form, eventData); } )
- .bind('form:validate:pass', function (eventData) { ClientSideValidations.callbacks.form.pass( form, eventData); } )
+ Copyright (c) 2012 Brian Cardarella
+ Licensed under the MIT license
+ http://www.opensource.org/licenses/mit-license.php
+*/
- // Set up the events for each validatable form element
- .find('[data-validate="true"]:input:enabled:not(:radio)')
- .live('focusout', function () { $(this).isValid(settings.validators); })
- .live('change', function () { $(this).data('changed', true); })
- // Callbacks
- .live('element:validate:after', function (eventData) { ClientSideValidations.callbacks.element.after( $(this), eventData); })
- .live('element:validate:before', function (eventData) { ClientSideValidations.callbacks.element.before($(this), eventData); })
- .live('element:validate:fail', function (eventData, message) {
- var element = $(this);
- ClientSideValidations.callbacks.element.fail(element, message, function () {
- addError(element, message);
- }, eventData); })
- .live('element:validate:pass', function (eventData) {
- var element = $(this);
- ClientSideValidations.callbacks.element.pass(element, function () {
- removeError(element);
- }, eventData); })
- // Checkboxes - Live events don't support filter
- .end().find('[data-validate="true"]:checkbox')
- .live('click', function () { $(this).isValid(settings.validators); })
- // Inputs for confirmations
- .end().find('[id*=_confirmation]').each(function () {
- var confirmationElement = $(this),
- element = form.find('#' + this.id.match(/(.+)_confirmation/)[1] + '[data-validate="true"]:input');
+(function() {
+ var $, validateElement, validateForm;
- if (element[0]) {
- $('#' + confirmationElement.attr('id'))
- .live('focusout', function () {
- element.data('changed', true).isValid(settings.validators);
- })
- .live('keyup', function () {
- element.data('changed', true).isValid(settings.validators);
- });
- }
- });
+ $ = jQuery;
+ $.fn.validate = function() {
+ return this.filter('form[data-validate]').each(function() {
+ var addError, form, removeError, settings;
+ form = $(this);
+ settings = window['ClientSideValidations']['forms'][form.attr('id')];
+ addError = function(element, message) {
+ return ClientSideValidations.formBuilders[settings.type].add(element, settings, message);
+ };
+ removeError = function(element) {
+ return ClientSideValidations.formBuilders[settings.type].remove(element, settings);
+ };
+ return form.submit(function() {
+ return form.isValid(settings.validators);
+ }).bind('ajax:beforeSend', function(eventData) {
+ if (eventData.target === this) return form.isValid(settings.validators);
+ }).bind('form:validate:after', function(eventData) {
+ return ClientSideValidations.callbacks.form.after(form, eventData);
+ }).bind('form:validate:before', function(eventData) {
+ return ClientSideValidations.callbacks.form.before(form, eventData);
+ }).bind('form:validate:fail', function(eventData) {
+ return ClientSideValidations.callbacks.form.fail(form, eventData);
+ }).bind('form:validate:pass', function(eventData) {
+ return ClientSideValidations.callbacks.form.pass(form, eventData);
+ }).find('[data-validate="true"]:input:enabled:not(:radio)').live('focusout', function() {
+ return $(this).isValid(settings.validators);
+ }).live('change', function() {
+ return $(this).data('changed', true);
+ }).live('element:validate:after', function(eventData) {
+ return ClientSideValidations.callbacks.element.after($(this), eventData);
+ }).live('element:validate:before', function(eventData) {
+ return ClientSideValidations.callbacks.element.before($(this), eventData);
+ }).live('element:validate:fail', function(eventData, message) {
+ var element;
+ element = $(this);
+ return ClientSideValidations.callbacks.element.fail(element, message, function() {
+ return addError(element, message);
+ }, eventData);
+ }).live('element:validate:pass', function(eventData, message) {
+ var element;
+ element = $(this);
+ return ClientSideValidations.callbacks.element.pass(element, function() {
+ return removeError(element);
+ }, eventData);
+ }).end().find('[data-validate="true"]:checkbox').live('click', function() {
+ return $(this).isValid(settings.validators);
+ }).end().find('[id*=_confirmation]').each(function() {
+ var confirmationElement, element;
+ confirmationElement = $(this);
+ element = form.find("#" + (this.id.match(/(.+)_confirmation/)[1]) + "[data-validate='true']:input");
+ if (element[0]) {
+ return $("#" + (confirmationElement.attr('id'))).live('focusout', function() {
+ return element.data('changed', true).isValid(settings.validators);
+ }).live('keyup', function() {
+ return element.data('changed', true).isValid(settings.validators);
+ });
+ }
+ });
});
};
- $.fn.isValid = function (validators) {
+ $.fn.isValid = function(validators) {
if ($(this[0]).is('form')) {
return validateForm($(this[0]), validators);
} else {
@@ -76,300 +81,287 @@
}
};
- var validateForm = function (form, validators) {
- var valid = true;
-
+ validateForm = function(form, validators) {
+ var valid;
+ valid = true;
form.trigger('form:validate:before').find('[data-validate="true"]:input:enabled').each(function() {
- if (!$(this).isValid(validators)) { valid = false; }
+ if (!$(this).isValid(validators)) valid = false;
+ if (valid) {
+ form.trigger('form:validate:pass');
+ } else {
+ form.trigger('form:validate:fail');
+ }
+ return form.trigger('form:validate:after');
});
-
- if (valid) {
- form.trigger('form:validate:pass');
- } else {
- form.trigger('form:validate:fail');
- }
-
- form.trigger('form:validate:after');
return valid;
- },
- validateElement = function (element, validators) {
- element.trigger('element:validate:before');
-
- if (element.data('changed') !== false) {
- var valid = true;
- element.data('changed', false);
+ };
- // Because 'length' is defined on the list of validators we cannot call jQuery.each on
- for (kind in ClientSideValidations.validators.local) {
+ validateElement = function(element, validators) {
+ var kind, message, valid;
+ element.trigger('element:validate:before');
+ if (element.data('changed') !== false) {
+ valid = true;
+ element.data('changed', false);
+ for (kind in ClientSideValidations.validators.local) {
+ if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
+ element.trigger('element:validate:fail', message).data('valid', false);
+ valid = false;
+ break;
+ }
+ }
+ if (valid) {
+ for (kind in ClientSideValidations.validators.remote) {
if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
element.trigger('element:validate:fail', message).data('valid', false);
valid = false;
break;
}
}
-
- if (valid) {
- for (kind in ClientSideValidations.validators.remote) {
- if (validators[kind] && (message = ClientSideValidations.validators.all()[kind](element, validators[kind]))) {
- element.trigger('element:validate:fail', message).data('valid', false);
- valid = false;
- break;
- }
- }
- }
-
- if (valid) { element.data('valid', null); element.trigger('element:validate:pass'); }
}
-
+ if (valid) {
+ element.data('valid', null);
+ element.trigger('element:validate:pass');
+ }
element.trigger('element:validate:after');
- return element.data('valid') === false ? false : true;
- };
+ if (element.data('valid') === false) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ };
- // Main hook
- // If new forms are dynamically introduced into the DOM the .validate() method
- // must be invoked on that form
- $(function () { $('form[data-validate]').validate(); });
-})(jQuery);
+ $(function() {
+ return $('form[data-validate]').validate();
+ });
-var ClientSideValidations = {
- forms: {},
- validators: {
- all: function() { return jQuery.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote); },
- local: {
- presence: function (element, options) {
- if (/^\s*$/.test(element.val() || "")) {
- return options.message;
- }
+ window.ClientSideValidations = {
+ forms: {},
+ validators: {
+ all: function() {
+ return $.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote);
},
- acceptance: function (element, options) {
- switch (element.attr('type')) {
- case 'checkbox':
- if (!element.attr('checked')) {
- return options.message;
- }
- break;
- case 'text':
- if (element.val() != (options.accept || '1')) {
+ local: {
+ presence: function(element, options) {
+ if (/^\s*$/.test(element.val() || "")) return options.message;
+ },
+ acceptance: function(element, options) {
+ switch (element.attr('type')) {
+ case 'checkbox':
+ if (!element.attr('checked')) return options.message;
+ break;
+ case 'text':
+ if (element.val() !== (options.accept || '1').toString()) {
+ return options.message;
+ }
+ }
+ },
+ format: function(element, options) {
+ var message;
+ if ((message = this.presence(element, options)) && options.allow_blank === true) {} else if (message) {
+ return message;
+ } else {
+ if (options['with'] && !options['with'].test(element.val())) {
return options.message;
}
- break;
- }
- },
- format: function (element, options) {
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- if (options['with'] && !options['with'].test(element.val())) {
- return options.message;
- } else if (options['without'] && options['without'].test(element.val())) {
- return options.message;
}
- }
- },
- numericality: function (element, options) {
- if (!/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/.test(element.val())) {
- return options.messages.numericality;
- }
-
- if (options.only_integer && !/^[+-]?\d+$/.test(element.val())) {
- return options.messages.only_integer;
- }
-
- var CHECKS = { greater_than: '>', greater_than_or_equal_to: '>=',
- equal_to: '==', less_than: '<', less_than_or_equal_to: '<=' };
-
- for (check in CHECKS) {
- if (options[check] !== undefined && !(new Function("return " + element.val() + CHECKS[check] + options[check])())) {
- return options.messages[check];
+ },
+ numericality: function(element, options) {
+ var CHECKS, check;
+ if (!/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?$/.test(element.val())) {
+ return options.messages.numericality;
}
- }
-
- if (options.odd && !(parseInt(element.val(), 10) % 2)) {
- return options.messages.odd;
- }
-
- if (options.even && (parseInt(element.val(), 10) % 2)) {
- return options.messages.even;
- }
- },
- length: function (element, options) {
- var blankOptions = {},
- CHECKS = { is: '==', minimum: '>=', maximum: '<=' },
- tokenizer = options.js_tokenizer || "split('')",
- tokenized_length = new Function("element", "return (element.val()." + tokenizer + " || '').length;")(element);
- if (options.is) {
- blankOptions.message = options.messages.is;
- } else if (options.minimum) {
- blankOptions.message = options.messages.minimum;
- }
- if ((message = this.presence(element, blankOptions)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
+ if (options.only_integer && !/^[+-]?\d+$/.test(element.val())) {
+ return options.messages.only_integer;
+ }
+ CHECKS = {
+ greater_than: '>',
+ greater_than_or_equal_to: '>=',
+ equal_to: '==',
+ less_than: '<',
+ less_than_or_equal_to: '<='
+ };
for (check in CHECKS) {
- if (options[check] && !(new Function("return " + tokenized_length + CHECKS[check] + options[check])())) {
+ if (options[check] !== void 0 && !(new Function("return " + element.val() + CHECKS[check] + options[check])())) {
return options.messages[check];
}
}
- }
- },
- exclusion: function (element, options) {
- var lower = null, upper = null;
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- if (options['in']) {
- for (i = 0; i < options['in'].length; i = i + 1) {
- if (options['in'][i] == element.val()) {
+ if (options.odd && !(parseInt(element.val(), 10) % 2)) {
+ return options.messages.odd;
+ }
+ if (options.even && (parseInt(element.val(), 10) % 2)) {
+ return options.messages.even;
+ }
+ },
+ length: function(element, options) {
+ var CHECKS, blankOptions, check, message, tokenized_length, tokenizer;
+ blankOptions = {};
+ CHECKS = {
+ is: '==',
+ minimum: '>=',
+ maximum: '<='
+ };
+ tokenizer = options.js_tokenizer || "split('')";
+ tokenized_length = new Function("element", "return (element.val()." + tokenizer + " || '').length;")(element);
+ if (options.is) {
+ blankOptions.message = options.messages.is;
+ } else if (options.minimum) {
+ blankOptions.message = options.messages.minimum;
+ }
+ if ((message = this.presence(element, blankOptions)) && options.allow_blank === true) {} else if (message) {
+ return message;
+ } else {
+ for (check in CHECKS) {
+ if (options[check] && !(new Function("return " + tokenized_length + CHECKS[check] + options[check])())) {
+ return options.messages[check];
+ }
+ }
+ }
+ },
+ exclusion: function(element, options) {
+ var lower, message, upper, value;
+ lower = null;
+ upper = null;
+ if ((message = this.presence(element, options)) && options.allow_blank === true) {} else if (message) {
+ return message;
+ } else {
+ if (options['in']) {
+ for (value in options['in']) {
+ if (value.toString() === element.val()) return options.message;
+ }
+ } else if (options.range) {
+ lower = options.range[0];
+ upper = options.range[1];
+ if (element.val() >= lower && element.val() <= upper) {
return options.message;
}
}
- } else if (options.range) {
- lower = options.range[0];
- upper = options.range[1];
- if (element.val() >= lower && element.val() <= upper) {
+ }
+ },
+ inclusion: function(element, options) {
+ var lower, message, upper, value;
+ lower = null;
+ upper = null;
+ if ((message = this.presence(element, options)) && options.allow_blank === true) {} else if (message) {
+ return message;
+ } else {
+ if (options['in']) {
+ for (value in options['in']) {
+ if (value.toString() === element.val()) return;
+ }
return options.message;
+ } else if (options.range) {
+ lower = options.range[0];
+ upper = options.range[1];
+ if (element.val() >= lower && element.val() <= upper) {} else {
+ return options.message;
+ }
}
}
+ },
+ confirmation: function(element, options) {
+ if (element.val() !== $("#" + (element.attr('id')) + "_confirmation").val()) {
+ return options.message;
+ }
}
},
- inclusion: function (element, options) {
- var lower = null, upper = null;
- if ((message = this.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- if (options['in']) {
- for (i = 0; i < options['in'].length; i = i + 1) {
- if (options['in'][i] == element.val()) {
- return;
+ remote: {
+ uniqueness: function(element, options) {
+ var data, key, message, name, scoped_element;
+ if ((message = ClientSideValidations.validators.local.presence(element, options)) && options.allow_blank === true) {} else if (message) {
+ return message;
+ } else {
+ data = {};
+ name = null;
+ data.case_sensitive = !!options.case_sensitive;
+ if (options.id) data.id = options.id;
+ if (options.scope) {
+ data.scope = {};
+ for (key in options.scope) {
+ scoped_element = $('[name="' + element.attr('name').replace(/\[\w+\]$/, '[' + key + ']' + '"]'));
+ if (scoped_element[0] && scoped_element.val() !== options.scope[key]) {
+ data.scope[key] = scoped_element.val();
+ scoped_element.unbind("change." + element.id).bind("change." + element.id, function() {
+ element.trigger('change');
+ return element.trigger('focusout');
+ });
+ } else {
+ data.scope[key] = options.scope[key];
+ }
}
}
- return options.message;
- } else if (options.range) {
- lower = options.range[0];
- upper = options.range[1];
-
- if (element.val() >= lower && element.val() <= upper) {
- return;
+ if (/_attributes\]/.test(element.attr('name'))) {
+ name = element.attr('name').match(/\[\w+_attributes\]/g).pop().match(/\[(\w+)_attributes\]/).pop();
+ name += /(\[\w+\])$/.exec(element.attr('name'))[1];
} else {
+ name = element.attr('name');
+ }
+ if (options['class']) {
+ name = "" + options['class'] + "[" + (name.split('[')[1]);
+ }
+ data[name] = element.val();
+ if ($.ajax({
+ url: '/validators/uniqueness',
+ data: data,
+ async: false
+ }).status === 200) {
return options.message;
}
}
}
- },
- confirmation: function (element, options) {
- if (element.val() !== jQuery('#' + element.attr('id') + '_confirmation').val()) {
- return options.message;
- }
}
},
- remote: {
- uniqueness: function (element, options) {
- if ((message = ClientSideValidations.validators.local.presence(element, options)) && options.allow_blank === true) {
- return;
- } else if (message) {
- return message;
- } else {
- var data = {},
- name = null;
- data.case_sensitive = !!options.case_sensitive;
- if (options.id) {
- data.id = options.id;
+ formBuilders: {
+ 'ActionView::Helpers::FormBuilder': {
+ add: function(element, settings, message) {
+ var inputErrorField, label, labelErrorField;
+ if (element.data('valid') !== false && $("label.message[for='" + (element.attr('id')) + "']")[0] === void 0) {
+ inputErrorField = $(settings.input_tag);
+ labelErrorField = $(settings.label_tag);
+ label = $("label[for='" + (element.attr('id')) + "']:not(.message)");
+ if (element.attr('autofocus')) element.attr('autofocus', false);
+ element.before(inputErrorField);
+ inputErrorField.find('span#input_tag').replaceWith(element);
+ inputErrorField.find('label.message').attr('for', element.attr('id'));
+ labelErrorField.find('label.message').attr('for', element.attr('id'));
+ label.replaceWith(labelErrorField);
+ labelErrorField.find('label#label_tag').replaceWith(label);
}
-
- if (options.scope) {
- data.scope = {};
- for (key in options.scope) {
- var scoped_element = jQuery('[name="' + element.attr('name').replace(/\[\w+\]$/, '[' + key + ']' + '"]'));
- if (scoped_element[0] && scoped_element.val() !== options.scope[key]) {
- data.scope[key] = scoped_element.val();
- scoped_element.unbind('change.' + element.id).bind('change.' + element.id, function() { element.trigger('change'); element.trigger('focusout'); });
- } else {
- data.scope[key] = options.scope[key];
- }
- }
- }
-
- // Kind of a hack but this will isolate the resource name and attribute.
- // e.g. user[records_attributes][0][title] => records[title]
- // e.g. user[record_attributes][title] => record[title]
- // Server side handles classifying the resource properly
- if (/_attributes\]/.test(element.attr('name'))) {
- name = element.attr('name').match(/\[\w+_attributes\]/g).pop().match(/\[(\w+)_attributes\]/).pop();
- name += /(\[\w+\])$/.exec(element.attr('name'))[1];
- } else {
- name = element.attr('name');
- }
-
- // Override the name if a nested module class is passed
- if (options['class']) {
- name = options['class'] + '[' + name.split('[')[1];
- }
- data[name] = element.val();
-
- if (jQuery.ajax({
- url: '/validators/uniqueness',
- data: data,
- async: false
- }).status === 200) {
- return options.message;
+ return $("label.message[for='" + (element.attr('id')) + "']").text(message);
+ },
+ remove: function(element, settings) {
+ var errorFieldClass, inputErrorField, label, labelErrorField;
+ errorFieldClass = $(settings.input_tag).attr('class');
+ inputErrorField = element.closest("." + errorFieldClass);
+ label = $("label[for='" + (element.attr('id')) + "']:not(.message)");
+ labelErrorField = label.closest("." + errorFieldClass);
+ if (inputErrorField[0]) {
+ inputErrorField.find("#" + (element.attr('id'))).detach();
+ inputErrorField.replaceWith(element);
+ label.detach();
+ return labelErrorField.replaceWith(label);
}
}
}
- }
- },
- formBuilders: {
- 'ActionView::Helpers::FormBuilder': {
- add: function (element, settings, message) {
- if (element.data('valid') !== false && jQuery('label.message[for="' + element.attr('id') + '"]')[0] === undefined) {
- var inputErrorField = jQuery(settings.input_tag),
- labelErrorField = jQuery(settings.label_tag),
- label = jQuery('label[for="' + element.attr('id') + '"]:not(.message)');
-
- if (element.attr('autofocus')) { element.attr('autofocus', false); }
- element.before(inputErrorField);
- inputErrorField.find('span#input_tag').replaceWith(element);
- inputErrorField.find('label.message').attr('for', element.attr('id'));
- labelErrorField.find('label.message').attr('for', element.attr('id'));
- label.replaceWith(labelErrorField);
- labelErrorField.find('label#label_tag').replaceWith(label);
+ },
+ callbacks: {
+ element: {
+ after: function(element, eventData) {},
+ before: function(element, eventData) {},
+ fail: function(element, message, addError, eventData) {
+ return addError();
+ },
+ pass: function(element, removeError, eventData) {
+ return removeError();
}
- jQuery('label.message[for="' + element.attr('id') + '"]').text(message);
},
- remove: function (element, settings) {
- var errorFieldClass = jQuery(settings.input_tag).attr('class'),
- inputErrorField = element.closest('.' + errorFieldClass),
- label = jQuery('label[for="' + element.attr('id') + '"]:not(.message)'),
- labelErrorField = label.closest('.' + errorFieldClass);
-
- if (inputErrorField[0]) {
- inputErrorField.find('#' + element.attr('id')).detach();
- inputErrorField.replaceWith(element);
- label.detach();
- labelErrorField.replaceWith(label);
- }
+ form: {
+ after: function(form, eventData) {},
+ before: function(form, eventData) {},
+ fail: function(form, eventData) {},
+ pass: function(form, eventData) {}
}
- },
- },
- callbacks: {
- element: {
- after: function (element, eventData) { },
- before: function (element, eventData) { },
- fail: function (element, message, addError, eventData) { addError(); },
- pass: function (element, removeError, eventData) { removeError(); }
- },
- form: {
- after: function (form, eventData) { },
- before: function (form, eventData) { },
- fail: function (form, eventData) { },
- pass: function (form, eventData) { }
}
- }
-};
+ };
+
+}).call(this);
Please sign in to comment.
Something went wrong with that request. Please try again.