Permalink
Browse files

NumericInput now provides default min, max and step attributes by ref…

…lecting on numericality validations
  • Loading branch information...
justinfrench committed Apr 4, 2011
1 parent 050ed23 commit d6e62654e8de574f10ef789a64d3a7fded5a1c76
View
@@ -14,6 +14,7 @@
* Massive refactoring of formtastic.rb into around 30 files and modules like StringInput and ButtonsHelper
* Removed chunks of Rails 2 specific code, tests and documentation
* Added support for HTML5 required attribute on input, select and textarea tags
+* Added support for HTML5 min/max/step attributes on NumericInput
1.2.4.beta (unreleased)
View
@@ -63,7 +63,7 @@ h2. It's better than _SomeOtherFormBuilder_ because...
* it doesn't hijack or change any of the standard Rails form inputs, so you can still use them as expected (even mix and match).
* it's got absolutely awesome spec coverage.
* there's a bunch of people using and working on it (it's not just one developer building half a solution).
-* it has growing HTML5 support (new inputs like email, new attributes like required)
+* it has growing HTML5 support (new inputs like email, new attributes like required/min/max/step)
h2. Why?
@@ -42,6 +42,43 @@ def validation_limit
end
end
+ # Prefer :greater_than_or_equal_to over :greater_than, for no particular reason.
+ def validation_min
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+ if validation
+ return validation.options[:greater_than_or_equal_to] if validation.options[:greater_than_or_equal_to]
+ return (validation.options[:greater_than] + 1) if validation.options[:greater_than]
+ else
+ nil
+ end
+ end
+
+ # Prefer :less_than_or_equal_to over :less_than, for no particular reason.
+ def validation_max
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+ if validation
+ return validation.options[:less_than_or_equal_to] if validation.options[:less_than_or_equal_to]
+ return (validation.options[:less_than] - 1) if validation.options[:less_than]
+ else
+ nil
+ end
+ end
+
+ def validation_integer_only?
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+ if validation
+ validation.options[:only_integer]
+ else
+ false
+ end
+ end
+
def validations?
!validations.empty?
end
@@ -25,10 +25,29 @@ module Inputs
# </fieldset>
# </form>
#
- # @example Pass new HTML5 attrbutes down to the `<input>` tag
- # <%= f.input :shoe_size, :as => :numeric, :input_html => { :min => 3, :max => 15, :step => 1 } %>
+ # @example Default HTML5 min/max/step attributes are detected from the numericality validations
+ #
+ # class Person < ActiveRecord::Base
+ # validates_numericality_of :age,
+ # :less_than => 100,
+ # :greater_than => 17,
+ # :only_integer => true
+ # end
+ #
+ # <%= f.input :age, :as => :numeric %>
+ #
+ # <li class="numeric">
+ # <label for="persom_age">Age</label>
+ # <input type="number" id="person_age" name="person[age]" min="18" max="99" step="1">
+ # </li>
+ #
+ # @example Pass attributes down to the `<input>` tag
+ # <%= f.input :shoe_size, :as => :numeric, :input_html => { :min => 3, :max => 15, :step => 1, :class => "special" } %>
#
# @see Formtastic::Helpers::InputsHelper#input InputsHelper#input for full documetation of all possible options.
+ # @see http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_numericality_of Rails' Numericality validation documentation
+ #
+ # @todo Rename/Alias to NumberInput
class NumericInput
include Base
include Base::Stringish
@@ -39,6 +58,15 @@ def to_html
builder.number_field(method, input_html_options)
end
end
+
+ def input_html_options
+ {
+ :min => validation_min,
+ :max => validation_max,
+ :step => validation_integer_only? ? 1 : nil
+ }.merge(super)
+ end
+
end
end
end
@@ -61,5 +61,132 @@
end
end
+ describe "when validations require a minimum value (:greater_than)" do
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :greater_than=>2})
+ ])
+ end
+
+ it "should add a max attribute to the input one greater than the validation" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@min="3"]')
+ end
+
+ it "should allow :input_html to override :min" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric, :input_html => { :min => 102 })
+ end)
+ output_buffer.should have_tag('input[@min="102"]')
+ end
+ end
+
+ describe "when validations require a minimum value (:greater_than_or_equal_to)" do
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :greater_than_or_equal_to=>2})
+ ])
+ end
+
+ it "should add a max attribute to the input equal to the validation" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@min="2"]')
+ end
+ end
+
+ describe "when validations require a maximum value (:less_than)" do
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :less_than=>20})
+ ])
+ end
+
+ it "should add a min attribute to the input one less than the validation" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@max="19"]')
+ end
+
+ it "should allow :input_html to override :min" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric, :input_html => { :max => 48 })
+ end)
+ output_buffer.should have_tag('input[@max="48"]')
+ end
+ end
+
+ describe "when validations require a maximum value (:less_than_or_equal_to)" do
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :less_than_or_equal_to=>20})
+ ])
+ end
+
+ it "should add a min attribute to the input one less than the validation" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@max="20"]')
+ end
+ end
+
+ describe "when validations require conflicting minimum values (:greater_than, :greater_than_or_equal_to)" do
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :greater_than => 20, :greater_than_or_equal_to=>2})
+ ])
+ end
+
+ it "should add a max attribute to the input equal to the :greater_than_or_equal_to validation" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@min="2"]')
+ end
+ end
+
+ describe "when validations require conflicting maximum values (:less_than, :less_than_or_equal_to)" do
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :less_than => 20, :less_than_or_equal_to=>2})
+ ])
+ end
+
+ it "should add a max attribute to the input equal to the :greater_than_or_equal_to validation" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@max="2"]')
+ end
+ end
+
+ describe "when validations require only an integer (:only_integer)" do
+
+ before do
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
+ active_model_numericality_validator([:title], {:allow_nil=>false, :only_integer=>true})
+ ])
+ end
+
+ it "should add a step=1 attribute to the input to signify that only whole numbers are allowed" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric)
+ end)
+ output_buffer.should have_tag('input[@step="1"]')
+ end
+
+ it "should let input_html override :step" do
+ concat(semantic_form_for(@new_post) do |builder|
+ builder.input(:title, :as => :numeric, :input_html => { :step => 3 })
+ end)
+ output_buffer.should have_tag('input[@step="3"]')
+ end
+ end
+
end
View
@@ -64,6 +64,10 @@ def active_model_length_validator(attributes, options = {})
def active_model_inclusion_validator(attributes, options = {})
active_model_validator(:inclusion, attributes, options)
end
+
+ def active_model_numericality_validator(attributes, options = {})
+ active_model_validator(:numericality, attributes, options)
+ end
class ::Post
extend ActiveModel::Naming if defined?(ActiveModel::Naming)

0 comments on commit d6e6265

Please sign in to comment.