Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

First import

  • Loading branch information...
commit 8c9313a70bb5454f0fc673d653ba29f1ec914d5e 0 parents
@porras porras authored
4 README
@@ -0,0 +1,4 @@
+LiveValidation
+==============
+
+Description goes here
22 Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the live_validation plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the live_validation plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'LiveValidation'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
312 assets/javascripts/live_validation.js
@@ -0,0 +1,312 @@
+// Copyright (c) 2007 Alec Hill (www.livevalidation.com) - LiveValidation is licensed under the terms of the MIT License
+
+var LiveValidation = Class.create();
+Object.extend(LiveValidation, {
+ TEXTAREA: 1,
+ TEXT: 2,
+ PASSWORD : 3,
+ CHECKBOX: 4,
+ massValidate: function(validations){
+ var returnValue = true;
+ for(var i = 0, len = validations.length; i < len; ++i ){
+ var valid = validations[i].validate();
+ if(returnValue) returnValue = valid;
+ }
+ return returnValue;
+ }
+});
+LiveValidation.prototype = {
+ validClass: 'LV_valid',
+ invalidClass: 'LV_invalid',
+ messageClass: 'LV_validation_message',
+ validFieldClass: 'LV_valid_field',
+ invalidFieldClass: 'LV_invalid_field',
+ displayMessageWhenEmpty: false,
+ initialize: function(element, optionsObj){
+ if(!element) throw new Error("LiveValidation::initialize - No element reference or element id has been provided!");
+ this.element = $(element);
+ if(!this.element) throw new Error("LiveValidation::initialize - No element with reference or id of '" + element + "' exists!");
+ this.elementType = this.getElementType();
+ this.validations = [];
+ // overwrite the options defaults with passed in ones
+ this.options = Object.extend({
+ validMessage: 'Thankyou!',
+ onValid: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); },
+ onInvalid: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); },
+ insertAfterWhatNode: this.element
+ }, optionsObj || {});
+ this.options.insertAfterWhatNode = $(this.options.insertAfterWhatNode);
+ Object.extend(this, this.options);
+ if(this.elementType == LiveValidation.CHECKBOX){
+ Event.observe(this.element, 'change', this.validate.bindAsEventListener(this));
+ Event.observe(this.element, 'click', this.validate.bindAsEventListener(this));
+ }else{
+ Event.observe(this.element, 'keyup', this.validate.bindAsEventListener(this));
+ Event.observe(this.element, 'blur', this.validate.bindAsEventListener(this));
+ }
+ },
+ add: function(validationFunction, validationParamsObj){
+ this.validations.push( {type: validationFunction, params: validationParamsObj || {} } );
+ return this;
+ },
+ getElementType: function(){
+ switch(true){
+ case (this.element.nodeName == 'TEXTAREA'):
+ return LiveValidation.TEXTAREA;
+ case (this.element.nodeName == 'INPUT' && this.element.type == 'text'):
+ return LiveValidation.TEXT;
+ case (this.element.nodeName == 'INPUT' && this.element.type == 'password'):
+ return LiveValidation.PASSWORD;
+ case (this.element.nodeName == 'INPUT' && this.element.type == 'checkbox'):
+ return LiveValidation.CHECKBOX;
+ case (this.element.nodeName == 'INPUT'):
+ throw new Error('LiveValidation::getElementType - Cannot use LiveValidation on an ' + this.element.type + ' input!');
+ default:
+ throw new Error('LiveValidation::getElementType - Element must be an input or textarea!');
+ }
+ },
+ doValidations: function(){
+ this.validationFailed = false;
+ for(var i = 0, len = this.validations.length; i < len; ++i){
+ var validation = this.validations[i];
+ switch(validation.type){
+ case Validate.Presence:
+ case Validate.Confirmation:
+ case Validate.Acceptance:
+ this.displayMessageWhenEmpty = true;
+ this.validationFailed = !this.validateElement(validation.type, validation.params);
+ break;
+ default:
+ this.validationFailed = !this.validateElement(validation.type, validation.params);
+ break;
+ }
+ if(this.validationFailed) return false;
+ }
+ this.message = this.validMessage;
+ return true;
+ },
+ validateElement: function(validationFunction, validationParamsObj){
+ var value = this.element.value;
+ if(validationFunction == Validate.Acceptance){
+ if(this.elementType != LiveValidation.CHECKBOX) throw new Error('LiveValidation::validateElement - Element to validate acceptance must be a checkbox!');
+ value = this.element.checked;
+ }
+ var isValid = true;
+ try{
+ validationFunction(value, validationParamsObj);
+ } catch(error) {
+ if(error instanceof Validate.Error){
+ if( value !== '' || (value === '' && this.displayMessageWhenEmpty) ){
+ this.validationFailed = true;
+ this.message = error.message;
+ isValid = false;
+ }
+ }else{
+ throw error;
+ }
+ }finally{
+ return isValid;
+ }
+ },
+ validate: function(){
+ var isValid = this.doValidations();
+ if(isValid){
+ this.onValid();
+ return true;
+ }else{
+ this.onInvalid();
+ return false;
+ }
+ },
+ createMessageSpan: function(){
+ var span = document.createElement('span');
+ var textNode = document.createTextNode(this.message);
+ span.appendChild(textNode);
+ return span;
+ },
+ insertMessage: function(elementToInsert){
+ if( nxtEl = this.insertAfterWhatNode.next('.' + this.messageClass) ) nxtEl.remove();
+ var className = this.validationFailed ? this.invalidClass : this.validClass;
+ if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value == '')) || this.element.value != '' ){
+ $(elementToInsert).addClassName( this.messageClass + (' ' + className) );
+ if( nxtSibling = this.insertAfterWhatNode.nextSibling){
+ this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, nxtSibling);
+ }else{
+ this.insertAfterWhatNode.parentNode.appendChild(elementToInsert);
+ }
+ }
+ },
+ addFieldClass: function(){
+ if(!this.validationFailed){
+ this.element.removeClassName(this.invalidFieldClass);
+ if(!this.displayMessageWhenEmpty && this.element.value == ''){
+ this.element.removeClassName(this.validFieldClass);
+ }else{
+ if(!this.element.hasClassName(this.validFieldClass)) this.element.addClassName(this.validFieldClass);
+ }
+ }else{
+ this.element.removeClassName(this.validFieldClass);
+ if(!this.element.hasClassName(this.invalidFieldClass)) this.element.addClassName(this.invalidFieldClass);
+ }
+ }
+}
+var Validate = {
+ Presence: function(value, paramsObj){
+ var params = Object.extend({
+ failureMessage: "Can't be empty!"
+ }, paramsObj || {});
+ if(value === '' || value === null || value === undefined) Validate.fail(params.failureMessage);
+ return true;
+ },
+ Numericality: function(value, paramsObj){
+ var suppliedValue = value;
+ var value = Number(value);
+ var paramsObj = paramsObj || {};
+ var params = {
+ notANumberMessage: paramsObj.notANumberMessage || "Must be a number!",
+ notAnIntegerMessage: paramsObj.notAnIntegerMessage || "Must be an integer!",
+ wrongNumberMessage: paramsObj.wrongNumberMessage || "Must be " + paramsObj.is + "!",
+ tooLowMessage: paramsObj.tooLowMessage || "Must not be less than " + paramsObj.minimum + "!",
+ tooHighMessage: paramsObj.tooHighMessage || "Must not be more than " + paramsObj.maximum + "!",
+ is: ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null,
+ minimum: ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null,
+ maximum: ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null,
+ onlyInteger: paramsObj.onlyInteger || false
+ };
+ if (!isFinite(value)) Validate.fail(params.notANumberMessage);
+ if (params.onlyInteger && ( ( /\.0+$|\.$/.test(String(suppliedValue)) ) || ( value != parseInt(value) ) ) ) Validate.fail(params.notAnIntegerMessage);
+ switch(true){
+ case (params.is !== null):
+ if( value != Number(params.is) ) Validate.fail(params.wrongNumberMessage);
+ break;
+ case (params.minimum !== null && params.maximum !== null):
+ Validate.Numericality(value, {tooLowMessage: params.tooLowMessage, minimum: params.minimum});
+ Validate.Numericality(value, {tooHighMessage: params.tooHighMessage, maximum: params.maximum});
+ break;
+ case (params.minimum !== null):
+ if( value < Number(params.minimum) ) Validate.fail(params.tooLowMessage);
+ break;
+ case (params.maximum !== null):
+ if( value > Number(params.maximum) ) Validate.fail(params.tooHighMessage);
+ break;
+ }
+ return true;
+ },
+ Format: function(value, paramsObj){
+ var value = String(value);
+ var params = Object.extend({
+ failureMessage: "Not valid!",
+ pattern: /./
+ }, paramsObj || {});
+ if(!params.pattern.test(value) /* && value != ''*/ ) Validate.fail(params.failureMessage);
+ return true;
+ },
+ Email: function(value, paramsObj){
+ var params = Object.extend({
+ failureMessage: "Must be a valid email address!"
+ }, paramsObj || {});
+ Validate.Format(value, { failureMessage: params.failureMessage, pattern: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i } );
+ return true;
+ },
+ Length: function(value, paramsObj){
+ var value = String(value);
+ var paramsObj = paramsObj || {};
+ var params = {
+ wrongLengthMessage: paramsObj.wrongLengthMessage || "Must be " + paramsObj.is + " characters long!",
+ tooShortMessage: paramsObj.tooShortMessage || "Must not be less than " + paramsObj.minimum + " characters long!",
+ tooLongMessage: paramsObj.tooLongMessage || "Must not be more than " + paramsObj.maximum + " characters long!",
+ is: ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null,
+ minimum: ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null,
+ maximum: ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null
+ }
+ switch(true){
+ case (params.is !== null):
+ if( value.length != Number(params.is) ) Validate.fail(params.wrongLengthMessage);
+ break;
+ case (params.minimum !== null && params.maximum !== null):
+ Validate.Length(value, {tooShortMessage: params.tooShortMessage, minimum: params.minimum});
+ Validate.Length(value, {tooLongMessage: params.tooLongMessage, maximum: params.maximum});
+ break;
+ case (params.minimum !== null):
+ if( value.length < Number(params.minimum) ) Validate.fail(params.tooShortMessage);
+ break;
+ case (params.maximum !== null):
+ if( value.length > Number(params.maximum) ) Validate.fail(params.tooLongMessage);
+ break;
+ default:
+ throw new Error("Validate::Length - Length(s) to validate against must be provided!");
+ }
+ return true;
+ },
+ Inclusion: function(value, paramsObj){
+ var params = Object.extend({
+ failureMessage: "Must be included in the list!",
+ within: [],
+ allowNull: false,
+ partialMatch: false,
+ exclusion: false
+ }, paramsObj || {});
+ if(params.allowNull && value == null) return true;
+ if(!params.allowNull && value == null) Validate.fail(params.failureMessage);
+ var found = (params.within.indexOf(value) == -1) ? false : true;
+ if(params.partialMatch){
+ found = false;
+ params.within.each( function(arrayVal){
+ if(value.indexOf(arrayVal) != -1 ) found = true;
+ });
+ }
+ if( (!params.exclusion && !found) || (params.exclusion && found) ) Validate.fail(params.failureMessage);
+ return true;
+ },
+ Exclusion: function(value, paramsObj){
+ var params = Object.extend({
+ failureMessage: "Must not be included in the list!",
+ within: [],
+ allowNull: false,
+ partialMatch: false
+ }, paramsObj || {});
+ params.exclusion = true;// set outside of params so cannot be overridden
+ Validate.Inclusion(value, params);
+ return true;
+ },
+ Confirmation: function(value, paramsObj){
+ if(!paramsObj.match) throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided!");
+ var params = Object.extend({
+ failureMessage: "Does not match!",
+ match: null
+ }, paramsObj || {});
+ params.match = $(paramsObj.match);
+ if(!params.match) throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '" + params.match + "'!");
+ if(value != params.match.value) Validate.fail(params.failureMessage);
+ return true;
+ },
+ Acceptance: function(value, paramsObj){
+ var params = Object.extend({
+ failureMessage: "Must be accepted!"
+ }, paramsObj || {});
+ if(!value) Validate.fail(params.failureMessage);
+ return true;
+ },
+ now: function(validationFunction, value, validationParamsObj){
+ if(!validationFunction) throw new Error("Validate::now - Validation function must be provided!");
+ var isValid = true;
+ try{
+ validationFunction(value, validationParamsObj || {});
+ } catch(error) {
+ if(error instanceof Validate.Error){
+ isValid = false;
+ }else{
+ throw error;
+ }
+ }finally{
+ return isValid
+ }
+ },
+ Error: function(errorMessage){
+ this.message = errorMessage;
+ this.name = 'ValidationError';
+ },
+ fail: function(errorMessage){
+ throw new Validate.Error(errorMessage);
+ }
+}
2  init.rb
@@ -0,0 +1,2 @@
+require 'live_validations'
+require 'form_helpers'
1  install.rb
@@ -0,0 +1 @@
+# Install hook code here
38 lib/form_helpers.rb
@@ -0,0 +1,38 @@
+module ActionView
+ module Helpers
+ module FormHelper
+ def text_field_with_live_validations(object_name, method, options = {})
+ text_field_without_live_validations(object_name, method, options = {}) +
+ script_tags(live_validation(object_name, method))
+ end
+
+ alias_method_chain :text_field, :live_validations
+
+ private
+
+ def live_validation(object_name, method)
+ if validations = self.instance_variable_get("@#{object_name.to_s}").class.live_validations[method]
+ field_name = "#{object_name}_#{method}"
+ initialize_validator(field_name) +
+ validations.map do |type, configuration|
+ live_validation_code(field_name, type, configuration)
+ end.join(';')
+ else
+ ''
+ end
+ end
+
+ def initialize_validator(field_name)
+ "var #{field_name} = new LiveValidation('#{field_name}');"
+ end
+
+ def live_validation_code(field_name, type, configuration)
+ "#{field_name}.add(#{ActiveRecord::Validations::VALIDATION_METHODS[type]}, #{configuration.to_json})"
+ end
+
+ def script_tags(js_code = '')
+ ( js_code.blank? ? '' : "<script>#{js_code}</script>" )
+ end
+ end
+ end
+end
53 lib/live_validations.rb
@@ -0,0 +1,53 @@
+module ActiveRecord
+ module Validations
+ LIVE_VALIDATIONS_OPTIONS = {
+ :failureMessage => :message,
+ :pattern => :with
+ }
+
+ VALIDATION_METHODS = {
+ :presence => "Validate.Presence",
+ :numericality => "Validate.Numericality",
+ :format => "Validate.Format"
+ }
+
+
+ module ClassMethods
+
+ VALIDATION_METHODS.keys.each do |type|
+ define_method "validates_#{type}_of_with_live_validations".to_sym do |*attr_names|
+ send "validates_#{type}_of_without_live_validations".to_sym, *attr_names
+ define_validations(type, attr_names)
+ end
+ alias_method_chain "validates_#{type}_of".to_sym, :live_validations
+ end
+
+ def live_validations
+ @live_validations ||= {}
+ end
+
+ private
+
+ def define_validations(validation_type, attr_names)
+ configuration = map_configuration(attr_names.pop, validation_type) if attr_names.last.is_a?(Hash)
+ attr_names.each do |attr_name|
+ add_live_validation(attr_name, validation_type, configuration)
+ end
+ end
+
+ def add_live_validation(attr_name, type, configuration = {})
+ @live_validations ||= {}
+ @live_validations[attr_name] ||= {}
+ @live_validations[attr_name][type] = configuration
+ end
+
+ def map_configuration(configuration, type = nil)
+ LIVE_VALIDATIONS_OPTIONS.each do |live, rails|
+ configuration[live] = configuration.delete(rails)
+ end
+ configuration[:notANumberMessage] = configuration.delete(:failureMessage) if type == :numericality
+ configuration.reject {|k, v| v.nil? }
+ end
+ end
+ end
+end
15 tasks/live_validation_tasks.rake
@@ -0,0 +1,15 @@
+namespace :livevalidation do
+
+ PLUGIN_ROOT = File.dirname(__FILE__) + '/../'
+
+ desc 'Installs required javascript file to the public/javascripts directory.'
+ task :install do
+ FileUtils.cp Dir[PLUGIN_ROOT + '/assets/javascripts/*.js'], RAILS_ROOT + '/public/javascripts'
+ end
+
+ desc 'Removes the javascript for the plugin.'
+ task :remove do
+ FileUtils.rm %{live_validation.js}.collect { |f| RAILS_ROOT + "/public/javascripts/" + f }
+ end
+
+end
0  test/form_helpers_test.rb
No changes.
54 test/live_validations_test.rb
@@ -0,0 +1,54 @@
+require 'test/unit'
+require 'rubygems'
+require 'active_record'
+require File.dirname(__FILE__) + '/../lib/live_validations'
+
+ActiveRecord::Base.class_eval do
+ alias_method :save, :valid?
+ def self.columns() @columns ||= []; end
+
+ def self.column(name, sql_type = nil, default = nil, null = true)
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null)
+ end
+end
+
+class Resource < ActiveRecord::Base
+ column :id, :integer
+ column :name, :string
+ column :amount, :integer
+end
+
+class LiveValidationTest < Test::Unit::TestCase
+
+ def setup
+ Resource.class_eval do # reset live validations
+ @live_validations = {}
+ end
+ end
+
+ def test_live_validations_accessor
+ assert_kind_of(Hash, Resource.live_validations)
+ end
+
+ def test_without_validations
+ assert_equal({}, Resource.live_validations)
+ end
+
+ def test_presence
+ Resource.class_eval do
+ validates_presence_of :name, :message => "can't be blank"
+ end
+ assert_kind_of(Hash, Resource.live_validations[:name])
+ assert_kind_of(Hash, Resource.live_validations[:name][:presence])
+ assert_equal("can't be blank", Resource.live_validations[:name][:presence][:failureMessage])
+ end
+
+ def test_numericality
+ Resource.class_eval do
+ validates_numericality_of :amount, :message => "isn't a valid number"
+ end
+ assert_kind_of(Hash, Resource.live_validations[:amount])
+ assert_kind_of(Hash, Resource.live_validations[:amount][:numericality])
+ assert_equal("isn't a valid number", Resource.live_validations[:amount][:numericality][:notANumberMessage])
+ end
+end
1  uninstall.rb
@@ -0,0 +1 @@
+# Uninstall hook code here
Please sign in to comment.
Something went wrong with that request. Please try again.