Skip to content

Commit

Permalink
Merge branch 'range-input'
Browse files Browse the repository at this point in the history
  • Loading branch information
justinfrench committed May 11, 2011
2 parents b230629 + f7ca5f5 commit ff6aec1
Show file tree
Hide file tree
Showing 11 changed files with 1,036 additions and 64 deletions.
1 change: 1 addition & 0 deletions README.textile
Expand Up @@ -313,6 +313,7 @@ The Formtastic input types:
* @:phone@ - a text field (just like string). Default for columns with name matching @"phone"@ or @"fax"@. New in HTML5.
* @:search@ - a text field (just like string). Default for columns with name matching @"search"@. New in HTML5. Works on Safari.
* @:hidden@ - a hidden field. Creates a hidden field (added for compatibility).
* @:range@ - a slider field.

The comments in the code are pretty good for each of these (what it does, what the output is, what the options are, etc.) so go check it out.

Expand Down
5 changes: 3 additions & 2 deletions lib/formtastic/form_builder.rb
Expand Up @@ -30,7 +30,7 @@ def self.configure(name, value = nil)
configure :default_hint_class, 'inline-hints'

attr_reader :template

attr_reader :auto_index

include Formtastic::HtmlAttributes
Expand Down Expand Up @@ -74,4 +74,5 @@ def semantic_fields_for(record_or_name_or_array, *args, &block)

end

end
end

1 change: 1 addition & 0 deletions lib/formtastic/helpers.rb
Expand Up @@ -13,3 +13,4 @@ module Helpers
autoload :Reflection, 'formtastic/helpers/reflection'
end
end

2 changes: 2 additions & 0 deletions lib/formtastic/inputs.rb
Expand Up @@ -17,6 +17,7 @@ module Inputs
autoload :PasswordInput
autoload :PhoneInput
autoload :RadioInput
autoload :RangeInput
autoload :SearchInput
autoload :SelectInput
autoload :StringInput
Expand All @@ -27,3 +28,4 @@ module Inputs
autoload :UrlInput
end
end

42 changes: 37 additions & 5 deletions lib/formtastic/inputs/base/validations.rb
Expand Up @@ -3,6 +3,24 @@ module Inputs
module Base
module Validations

class IndeterminableMinimumAttributeError < ArgumentError
def message
[
"A minimum value can not be determined when the validation uses :greater_than on a :decimal or :float column type.",
"Please alter the validation to use :greater_than_or_equal_to, or provide a value for this attribute explicitly with the :min option on input()."
].join("\n")
end
end

class IndeterminableMaximumAttributeError < ArgumentError
def message
[
"A maximum value can not be determined when the validation uses :less_than on a :decimal or :float column type.",
"Please alter the validation to use :less_than_or_equal_to, or provide a value for this attribute explicitly with the :max option on input()."
].join("\n")
end
end

def validations
@validations ||= if object && object.class.respond_to?(:validators_on)
object.class.validators_on(attributized_method_name).select do |validator|
Expand Down Expand Up @@ -47,11 +65,13 @@ def validation_min
validation = validations? && validations.find do |validation|
validation.kind == :numericality
end
if validation

if validation
# We can't determine an appropriate value for :greater_than with a float/decimal column
raise IndeterminableMinimumAttributeError if validation.options[:greater_than] && column? && [:float, :decimal].include?(column.type)

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
return (validation.options[:greater_than] + 1) if validation.options[:greater_than]
end
end

Expand All @@ -61,8 +81,20 @@ def validation_max
validation.kind == :numericality
end
if validation
# We can't determine an appropriate value for :greater_than with a float/decimal column
raise IndeterminableMaximumAttributeError if validation.options[:less_than] && column? && [:float, :decimal].include?(column.type)

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]
return (validation.options[:less_than] - 1) if validation.options[:less_than]
end
end

def validation_step
validation = validations? && validations.find do |validation|
validation.kind == :numericality
end
if validation
validation.options[:step]
else
nil
end
Expand Down
68 changes: 57 additions & 11 deletions lib/formtastic/inputs/number_input.rb
Expand Up @@ -6,6 +6,20 @@ module Inputs
# and `:decimal`, as well as `:integer` columns that aren't used for `belongs_to` associations,
# but can be applied to any text-like input with `:as => :number`.
#
# Sensible default values for the `min`, `max` and `step` attributes are found by reflecting on
# the model's validations (when provided). An `IndeterminableMinimumAttributeError` exception
# will be raised when the following conditions are all true:
#
# * you haven't specified a `:min` or `:max` for the input
# * the model's database column type is a `:float` or `:decimal`
# * the validation uses `:less_than` or `:greater_than`
#
# The solution is to either:
#
# * manually specify the `:min` or `:max` for the input
# * change the database column type to an `:integer` (if appropriate)
# * change the validations to use `:less_than_or_equal_to` or `:greater_than_or_equal_to`
#
# @example Full form context and output
#
# <%= semantic_form_for(@user) do |f| %>
Expand All @@ -29,25 +43,30 @@ module Inputs
#
# class Person < ActiveRecord::Base
# validates_numericality_of :age,
# :less_than => 100,
# :greater_than => 17,
# :less_than_or_equal_to => 100,
# :greater_than_or_equal_to => 18,
# :only_integer => true
# end
#
# <%= f.input :age, :as => :number %>
#
# <li class="numeric">
# <label for="persom_age">Age</label>
# <input type="number" id="person_age" name="person[age]" min="18" max="99" step="1">
# <input type="number" id="person_age" name="person[age]" min="18" max="100" step="1">
# </li>
#
# @example Pass attributes down to the `<input>` tag
# @example Pass attributes down to the `<input>` tag with :input_html
# <%= f.input :shoe_size, :as => :number, :input_html => { :min => 3, :max => 15, :step => 1, :class => "special" } %>
#
# @example Min/max/step also work as options
# <%= f.input :shoe_size, :as => :number, :min => 3, :max => 15, :step => 1, :input_html => { :class => "special" } %>
#
# @example Use :in with a Range as a shortcut for :min/:max
# <%= f.input :shoe_size, :as => :number, :in => 3..15, :step => 1 %>
# <%= f.input :shoe_size, :as => :number, :input_html => { :in => 3..15, :step => 1 } %>
#
# @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 NumberInput
include Base
include Base::Stringish
Expand All @@ -60,11 +79,38 @@ def to_html
end

def input_html_options
{
:min => validation_min,
:max => validation_max,
:step => validation_integer_only? ? 1 : nil
}.merge(super)
defaults = super

if in_option
defaults[:min] = in_option.to_a.min
defaults[:max] = in_option.to_a.max
else
defaults[:min] ||= min_option
defaults[:max] ||= max_option
end
defaults[:step] ||= step_option
defaults
end

def step_option
return options[:step] if options.key?(:step)
return validation_step if validation_step
return 1 if validation_integer_only?
1
end

def min_option
return options[:min] if options.key?(:min)
validation_min
end

def max_option
return options[:max] if options.key?(:max)
validation_max
end

def in_option
options[:in]
end

end
Expand Down
3 changes: 2 additions & 1 deletion lib/formtastic/inputs/numeric_input.rb
Expand Up @@ -17,4 +17,5 @@ def initialize(builder, template, object, object_name, method, options)

end
end
end
end

117 changes: 117 additions & 0 deletions lib/formtastic/inputs/range_input.rb
@@ -0,0 +1,117 @@
module Formtastic
module Inputs

# Outputs a simple `<label>` with a HTML5 `<input type="range">` wrapped in the standard
# `<li>` wrapper. This is an alternative input choice to a number input.
#
# Sensible default for the `min`, `max` and `step` attributes are found by reflecting on
# the model's validations. When validations are not provided, the `min` and `step` default to
# `1` and the `max` default to `100`. An `IndeterminableMinimumAttributeError` exception
# will be raised when the following conditions are all true:
#
# * you haven't specified a `:min` or `:max` for the input
# * the model's database column type is a `:float` or `:decimal`
# * the validation uses `:less_than` or `:greater_than`
#
# The solution is to either:
#
# * manually specify the `:min` or `:max` for the input
# * change the database column type to an `:integer` (if appropriate)
# * change the validations to use `:less_than_or_equal_to` or `:greater_than_or_equal_to`
#
# @example Full form context and output
#
# <%= semantic_form_for(@user) do |f| %>
# <%= f.inputs do %>
# <%= f.input :shoe_size, :as => :range %>
# <% end %>
# <% end %>
#
# <form...>
# <fieldset>
# <ol>
# <li class="numeric">
# <label for="user_shoe_size">Shoe size</label>
# <input type="range" id="user_shoe_size" name="user[shoe_size]" min="1" max="100" step="1">
# </li>
# </ol>
# </fieldset>
# </form>
#
# @example Default HTML5 min/max/step attributes are detected from the numericality validations
#
# class Person < ActiveRecord::Base
# validates_numericality_of :age,
# :less_than_or_equal_to => 100,
# :greater_than_or_equal_to => 18,
# :only_integer => true
# end
#
# <%= f.input :age, :as => :number %>
#
# <li class="numeric">
# <label for="persom_age">Age</label>
# <input type="range" id="person_age" name="person[age]" min="18" max="100" step="1">
# </li>
#
# @example Pass attributes down to the `<input>` tag with :input_html
# <%= f.input :shoe_size, :as => :range, :input_html => { :min => 3, :max => 15, :step => 1, :class => "special" } %>
#
# @example Min/max/step also work as options
# <%= f.input :shoe_size, :as => :range, :min => 3, :max => 15, :step => 1, :input_html => { :class => "special" } %>
#
# @example Use :in with a Range as a shortcut for :min/:max
# <%= f.input :shoe_size, :as => :range, :in => 3..15, :step => 1 %>
# <%= f.input :shoe_size, :as => :range, :input_html => { :in => 3..15, :step => 1 } %>
#
# @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
class RangeInput
include Base
include Base::Stringish

def to_html
input_wrapping do
label_html <<
builder.range_field(method, input_html_options)
end
end

def input_html_options
defaults = super

if in_option
defaults[:min] = in_option.to_a.min
defaults[:max] = in_option.to_a.max
else
defaults[:min] ||= min_option
defaults[:max] ||= max_option
end
defaults[:step] ||= step_option
defaults
end

def step_option
return options[:step] if options.key?(:step)
return validation_step if validation_step
return 1 if validation_integer_only?
1
end

def min_option
return options[:min] if options.key?(:min)
validation_min || 1
end

def max_option
return options[:max] if options.key?(:max)
validation_max || 100
end

def in_option
options[:in]
end

end
end
end

0 comments on commit ff6aec1

Please sign in to comment.