1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #6359 from carlosgaldino/add-html5-inputs

Add HTML5 color and date/time inputs
This commit is contained in:
Jeremy Kemper 2012-05-22 16:17:40 -07:00
commit 65d2a60a8c
13 changed files with 446 additions and 2 deletions

View file

@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
* Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`,
`datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino*
* Add `color_field` and `color_field_tag` helpers. *Carlos Galdino*
* `assert_generates`, `assert_recognizes`, and `assert_routing` all raise
`Assertion` instead of `RoutingError` *David Chelimsky*

View file

@ -939,6 +939,15 @@ module ActionView
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
# Returns a text_field of type "color".
#
# color_field("car", "color")
# # => <input id="car_color" name="car[color]" type="color" value="#000000" />
#
def color_field(object_name, method, options = {})
Tags::ColorField.new(object_name, method, self, options).render
end
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
# some browsers.
@ -1007,6 +1016,74 @@ module ActionView
Tags::TimeField.new(object_name, method, self, options).render
end
# Returns a text_field of type "datetime".
#
# datetime_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime" />
#
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
# on the object's value, which makes it behave as expected for instances
# of DateTime and ActiveSupport::TimeWithZone.
#
# @user.born_on = Date.new(1984, 1, 12)
# datetime_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
#
def datetime_field(object_name, method, options = {})
Tags::DatetimeField.new(object_name, method, self, options).render
end
# Returns a text_field of type "datetime-local".
#
# datetime_local_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
#
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
# on the object's value, which makes it behave as expected for instances
# of DateTime and ActiveSupport::TimeWithZone.
#
# @user.born_on = Date.new(1984, 1, 12)
# datetime_local_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
#
def datetime_local_field(object_name, method, options = {})
Tags::DatetimeLocalField.new(object_name, method, self, options).render
end
# Returns a text_field of type "month".
#
# month_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="month" />
#
# The default value is generated by trying to call +strftime+ with "%Y-%m"
# on the object's value, which makes it behave as expected for instances
# of DateTime and ActiveSupport::TimeWithZone.
#
# @user.born_on = Date.new(1984, 1, 27)
# month_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
#
def month_field(object_name, method, options = {})
Tags::MonthField.new(object_name, method, self, options).render
end
# Returns a text_field of type "week".
#
# week_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="week" />
#
# The default value is generated by trying to call +strftime+ with "%Y-W%W"
# on the object's value, which makes it behave as expected for instances
# of DateTime and ActiveSupport::TimeWithZone.
#
# @user.born_on = Date.new(1984, 5, 12)
# week_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
#
def week_field(object_name, method, options = {})
Tags::WeekField.new(object_name, method, self, options).render
end
# Returns a text_field of type "url".
#
# url_field("user", "homepage")

View file

@ -524,6 +524,14 @@ module ActionView
output.safe_concat("</fieldset>")
end
# Creates a text field of type "color".
#
# ==== Options
# * Accepts the same options as text_field_tag.
def color_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
end
# Creates a text field of type "search".
#
# ==== Options
@ -560,6 +568,50 @@ module ActionView
text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
end
# Creates a text field of type "datetime".
#
# === Options
# * <tt>:min</tt> - The minimum acceptable value.
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
end
# Creates a text field of type "datetime-local".
#
# === Options
# * <tt>:min</tt> - The minimum acceptable value.
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_local_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
end
# Creates a text field of type "month".
#
# === Options
# * <tt>:min</tt> - The minimum acceptable value.
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def month_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
end
# Creates a text field of type "week".
#
# === Options
# * <tt>:min</tt> - The minimum acceptable value.
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def week_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
end
# Creates a text field of type "url".
#
# ==== Options

View file

@ -8,14 +8,18 @@ module ActionView
autoload :CollectionCheckBoxes
autoload :CollectionRadioButtons
autoload :CollectionSelect
autoload :ColorField
autoload :DateField
autoload :DateSelect
autoload :DatetimeField
autoload :DatetimeLocalField
autoload :DatetimeSelect
autoload :EmailField
autoload :FileField
autoload :GroupedCollectionSelect
autoload :HiddenField
autoload :Label
autoload :MonthField
autoload :NumberField
autoload :PasswordField
autoload :RadioButton
@ -29,6 +33,7 @@ module ActionView
autoload :TimeSelect
autoload :TimeZoneSelect
autoload :UrlField
autoload :WeekField
end
end
end

View file

@ -0,0 +1,25 @@
module ActionView
module Helpers
module Tags
class ColorField < TextField #:nodoc:
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { validate_color_string(value(object)) }
@options = options
super
end
private
def validate_color_string(string)
regex = /#[0-9a-fA-F]{6}/
if regex.match(string)
string.downcase
else
"#000000"
end
end
end
end
end
end

View file

@ -0,0 +1,22 @@
module ActionView
module Helpers
module Tags
class DatetimeField < TextField #:nodoc:
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { format_global_date_time_string(value(object)) }
options["min"] = format_global_date_time_string(options["min"])
options["max"] = format_global_date_time_string(options["max"])
@options = options
super
end
private
def format_global_date_time_string(value)
value.try(:strftime, "%Y-%m-%dT%T.%L%z")
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module ActionView
module Helpers
module Tags
class DatetimeLocalField < TextField #:nodoc:
def render
options = @options.stringify_keys
options["type"] = "datetime-local"
options["value"] = @options.fetch("value") { format_local_date_time_string(value(object)) }
options["min"] = format_local_date_time_string(options["min"])
options["max"] = format_local_date_time_string(options["max"])
@options = options
super
end
private
def format_local_date_time_string(value)
value.try(:strftime, "%Y-%m-%dT%T")
end
end
end
end
end

View file

@ -0,0 +1,22 @@
module ActionView
module Helpers
module Tags
class MonthField < TextField #:nodoc:
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { format_month_string(value(object)) }
options["min"] = format_month_string(options["min"])
options["max"] = format_month_string(options["max"])
@options = options
super
end
private
def format_month_string(value)
value.try(:strftime, "%Y-%m")
end
end
end
end
end

View file

@ -0,0 +1,22 @@
module ActionView
module Helpers
module Tags
class WeekField < TextField #:nodoc:
def render
options = @options.stringify_keys
options["value"] = @options.fetch("value") { format_week_string(value(object)) }
options["min"] = format_week_string(options["min"])
options["max"] = format_week_string(options["max"])
@options = options
super
end
private
def format_week_string(value)
value.try(:strftime, "%Y-W%W")
end
end
end
end
end

View file

@ -214,3 +214,6 @@ class RenderJsonTestException < Exception
return { :error => self.class.name, :message => self.to_s }.to_json
end
end
class Car < Struct.new(:color)
end

View file

@ -83,6 +83,8 @@ class FormHelperTest < ActionView::TestCase
@post.tags << Tag.new
@blog_post = Blog::Post.new("And his name will be forty and four.", 44)
@car = Car.new("#000FFF")
end
Routes = ActionDispatch::Routing::RouteSet.new
@ -610,6 +612,17 @@ class FormHelperTest < ActionView::TestCase
)
end
def test_color_field_with_valid_hex_color_string
expected = %{<input id="car_color" name="car[color]" type="color" value="#000fff" />}
assert_dom_equal(expected, color_field("car", "color"))
end
def test_color_field_with_invalid_hex_color_string
expected = %{<input id="car_color" name="car[color]" type="color" value="#000000" />}
@car.color = "#1234TR"
assert_dom_equal(expected, color_field("car", "color"))
end
def test_search_field
expected = %{<input id="contact_notes_query" name="contact[notes_query]" type="search" />}
assert_dom_equal(expected, search_field("contact", "notes_query"))
@ -672,6 +685,146 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, time_field("post", "written_on"))
end
def test_datetime_field
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T00:00:00.000+0000" />}
assert_dom_equal(expected, datetime_field("post", "written_on"))
end
def test_datetime_field_with_datetime_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
assert_dom_equal(expected, datetime_field("post", "written_on"))
end
def test_datetime_field_with_extra_attrs
expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00.000+0000" min="2000-06-15T20:45:30.000+0000" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
step = 60
assert_dom_equal(expected, datetime_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
end
def test_datetime_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
assert_dom_equal(expected, datetime_field("post", "written_on"))
ensure
Time.zone = previous_time_zone
end
def test_datetime_field_with_nil_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" />}
@post.written_on = nil
assert_dom_equal(expected, datetime_field("post", "written_on"))
end
def test_datetime_local_field
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T00:00:00" />}
assert_dom_equal(expected, datetime_local_field("post", "written_on"))
end
def test_datetime_local_field_with_datetime_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
assert_dom_equal(expected, datetime_local_field("post", "written_on"))
end
def test_datetime_local_field_with_extra_attrs
expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
step = 60
assert_dom_equal(expected, datetime_local_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
end
def test_datetime_local_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T15:30:45" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
assert_dom_equal(expected, datetime_local_field("post", "written_on"))
ensure
Time.zone = previous_time_zone
end
def test_datetime_local_field_with_nil_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" />}
@post.written_on = nil
assert_dom_equal(expected, datetime_local_field("post", "written_on"))
end
def test_month_field
expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />}
assert_dom_equal(expected, month_field("post", "written_on"))
end
def test_month_field_with_nil_value
expected = %{<input id="post_written_on" name="post[written_on]" type="month" />}
@post.written_on = nil
assert_dom_equal(expected, month_field("post", "written_on"))
end
def test_month_field_with_datetime_value
expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
assert_dom_equal(expected, month_field("post", "written_on"))
end
def test_month_field_with_extra_attrs
expected = %{<input id="post_written_on" step="2" max="2010-12" min="2000-02" name="post[written_on]" type="month" value="2004-06" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = DateTime.new(2000, 2, 13)
max_value = DateTime.new(2010, 12, 23)
step = 2
assert_dom_equal(expected, month_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
end
def test_month_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
assert_dom_equal(expected, month_field("post", "written_on"))
ensure
Time.zone = previous_time_zone
end
def test_week_field
expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
assert_dom_equal(expected, week_field("post", "written_on"))
end
def test_week_field_with_nil_value
expected = %{<input id="post_written_on" name="post[written_on]" type="week" />}
@post.written_on = nil
assert_dom_equal(expected, week_field("post", "written_on"))
end
def test_week_field_with_datetime_value
expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
assert_dom_equal(expected, week_field("post", "written_on"))
end
def test_week_field_with_extra_attrs
expected = %{<input id="post_written_on" step="2" max="2010-W51" min="2000-W06" name="post[written_on]" type="week" value="2004-W24" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = DateTime.new(2000, 2, 13)
max_value = DateTime.new(2010, 12, 23)
step = 2
assert_dom_equal(expected, week_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
end
def test_week_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
assert_dom_equal(expected, week_field("post", "written_on"))
ensure
Time.zone = previous_time_zone
end
def test_url_field
expected = %{<input id="user_homepage" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))

View file

@ -444,6 +444,11 @@ class FormTagHelperTest < ActionView::TestCase
)
end
def test_color_field_tag
expected = %{<input id="car" name="car" type="color" />}
assert_dom_equal(expected, color_field_tag("car"))
end
def test_search_field_tag
expected = %{<input id="query" name="query" type="search" />}
assert_dom_equal(expected, search_field_tag("query"))
@ -464,6 +469,26 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, time_field_tag("cell"))
end
def test_datetime_field_tag
expected = %{<input id="appointment" name="appointment" type="datetime" />}
assert_dom_equal(expected, datetime_field_tag("appointment"))
end
def test_datetime_local_field_tag
expected = %{<input id="appointment" name="appointment" type="datetime-local" />}
assert_dom_equal(expected, datetime_local_field_tag("appointment"))
end
def test_month_field_tag
expected = %{<input id="birthday" name="birthday" type="month" />}
assert_dom_equal(expected, month_field_tag("birthday"))
end
def test_week_field_tag
expected = %{<input id="birthday" name="birthday" type="week" />}
assert_dom_equal(expected, week_field_tag("birthday"))
end
def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))

View file

@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with
h4. Other Helpers of Interest
Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, URL fields and email fields:
Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
@ -159,8 +159,13 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
<%= datetime_field(:user, :meeting_time) %>
<%= datetime_local_field(:user, :graduation_day) %>
<%= month_field(:user, :birthday_month) %>
<%= week_field(:user, :birthday_week) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
</erb>
@ -173,14 +178,19 @@ Output:
<input id="user_name" name="user[name]" type="search" />
<input id="user_phone" name="user[phone]" type="tel" />
<input id="user_born_on" name="user[born_on]" type="date" />
<input id="user_meeting_time" name="user[meeting_time]" type="datetime" />
<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" />
<input id="user_birthday_month" name="user[birthday_month]" type="month" />
<input id="user_birthday_week" name="user[birthday_week]" type="week" />
<input id="user_homepage" name="user[homepage]" type="url" />
<input id="user_address" name="user[address]" type="email" />
<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" />
<input id="task_started_at" name="task[started_at]" type="time" />
</html>
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
IMPORTANT: The search, telephone, date, time, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.