Skip to content

Commit

Permalink
Added all the new HTML5 form types as individual form tag methods (se…
Browse files Browse the repository at this point in the history
…arch, url, number, etc) (Closes #3646) [Stephen Celis]
  • Loading branch information
dhh committed Apr 5, 2010
1 parent 40a3e67 commit f8730e5
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 0 deletions.
3 changes: 3 additions & 0 deletions actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.0.0 [Edge] (pending)*

* Added all the new HTML5 form types as individual form tag methods (search, url, number, etc) #3646 [Stephen Celis]

* Changed the object used in routing constraints to be an instance of
ActionDispatch::Request rather than Rack::Request

Expand All @@ -13,6 +15,7 @@
"HEAD" and #request_method returns "GET" in HEAD requests). This
is for compatibility with Rack::Request


*Rails 3.0.0 [beta 2] (April 1st, 2010)*

* #concat is now deprecated in favor of using <%= %> helpers [YK]
Expand Down
58 changes: 58 additions & 0 deletions actionpack/lib/action_view/helpers/form_helper.rb
Expand Up @@ -784,6 +784,56 @@ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_
def radio_button(object_name, method, tag_value, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end

# Returns a text_field of type "search".
def search_field(object_name, method, options = {})
options = options.stringify_keys

if options["autosave"]
if options["autosave"] == true
options["autosave"] = request.host.split(".").reverse.join(".")
end
options["results"] ||= 10
end

if options["onsearch"]
options["incremental"] = true unless options.has_key?("incremental")
end

InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("search", options)
end

# Returns a text_field of type "tel".
def telephone_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
end
alias phone_field telephone_field

# Returns a text_field of type "url".
def url_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
end

# Returns a text_field of type "email".
def email_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
end

# Returns an input tag of type "number".
#
# ==== Options
# * Accepts same options as number_field_tag
def number_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
end

# Returns an input tag of type "range".
#
# ==== Options
# * Accepts same options as range_field_tag
def range_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
end
end

module InstanceTagMethods #:nodoc:
Expand Down Expand Up @@ -847,6 +897,14 @@ def to_input_field_tag(field_type, options = {})
tag("input", options)
end

def to_number_field_tag(field_type, options = {})
options = options.stringify_keys
if range = options.delete("in") || options.delete("within")
options.update("min" => range.min, "max" => range.max)
end
to_input_field_tag(field_type, options)
end

def to_radio_button_tag(tag_value, options = {})
options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
options["type"] = "radio"
Expand Down
63 changes: 63 additions & 0 deletions actionpack/lib/action_view/helpers/form_tag_helper.rb
Expand Up @@ -457,6 +457,69 @@ def field_set_tag(legend = nil, options = nil, &block)
output.safe_concat("</fieldset>")
end

# Creates a text field of type "search".
#
# ==== Options
# * Accepts the same options as text_field_tag.
def search_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
end

# Creates a text field of type "tel".
#
# ==== Options
# * Accepts the same options as text_field_tag.
def telephone_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
end
alias phone_field_tag telephone_field_tag

# Creates a text field of type "url".
#
# ==== Options
# * Accepts the same options as text_field_tag.
def url_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
end

# Creates a text field of type "email".
#
# ==== Options
# * Accepts the same options as text_field_tag.
def email_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
end

# Creates a number field.
#
# ==== Options
# * <tt>:min</tt> - The minimum acceptable value.
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
# <tt>:max</tt> values.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
#
# ==== Examples
# number_field_tag 'quantity', nil, :in => 1...10
# => <input id="quantity" name="quantity" min="1" max="9" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
if range = options.delete("in") || options.delete("within")
options.update("min" => range.min, "max" => range.max)
end
text_field_tag(name, value, options)
end

# Creates a range form element.
#
# ==== Options
# * Accepts the same options as number_field_tag.
def range_field_tag(name, value = nil, options = {})
number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
end

private
def html_options_for_form(url_for_options, options, *parameters_for_url)
returning options.stringify_keys do |html_options|
Expand Down
30 changes: 30 additions & 0 deletions actionpack/test/template/form_helper_test.rb
Expand Up @@ -349,6 +349,36 @@ def test_text_area_with_size_option
)
end

def search_field

This comment has been minimized.

Copy link
@pusewicz

pusewicz Apr 6, 2010

Shouldn't it be test_search_field?

This comment has been minimized.

Copy link
@josevalim

josevalim Apr 6, 2010

Contributor

Yup. I'm going to fix it, thanks!

expected = %{<input id="contact_notes_query" size="30" name="contact[notes_query]" type="search" />}
assert_dom_equal(expected, search_field("contact", "notes_query"))
end

def test_telephone_field
expected = %{<input id="user_cell" size="30" name="user[cell]" type="tel" />}
assert_dom_equal(expected, telephone_field("user", "cell"))

This comment has been minimized.

Copy link
@pusewicz

pusewicz Apr 6, 2010

It should probably test phone_field too?

This comment has been minimized.

Copy link
@arthurschreiber

arthurschreiber Apr 6, 2010

Contributor

phone_field is an alias to telephone_field, so this should be fine. :)

This comment has been minimized.

Copy link
@pusewicz

pusewicz Apr 6, 2010

Yeah, but there is not a test that it's result is exactly the same. Say someone changes it, created a method that does something different and you have a problem.

end

def test_url_field
expected = %{<input id="user_homepage" size="30" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
end

def test_email_field
expected = %{<input id="user_address" size="30" name="user[address]" type="email" />}
assert_dom_equal(expected, email_field("user", "address"))
end

def test_number_field
expected = %{<input name="order[quantity]" size="30" max="9" id="order_quantity" type="number" min="1" />}
assert_dom_equal(expected, number_field("order", "quantity", :in => 1...10))
end

def test_range_input
expected = %{<input name="hifi[volume]" step="0.1" size="30" max="11" id="hifi_volume" type="range" min="0" />}
assert_dom_equal(expected, range_field("hifi", "volume", :in => 0..11, :step => 0.1))
end

def test_explicit_name
assert_dom_equal(
'<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
Expand Down
30 changes: 30 additions & 0 deletions actionpack/test/template/form_tag_helper_test.rb
Expand Up @@ -335,6 +335,36 @@ def test_image_submit_tag_with_confirmation
)
end

def test_search_field_tag
expected = %{<input id="query" name="query" type="search" />}
assert_dom_equal(expected, search_field_tag("query"))
end

def telephone_field_tag
expected = %{<input id="cell" name="cell" type="tel" />}
assert_dom_equal(expected, telephone_field_tag("cell"))
end

def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))
end

def test_email_field_tag
expected = %{<input id="address" name="address" type="email" />}
assert_dom_equal(expected, email_field_tag("address"))
end

def test_number_field_tag
expected = %{<input name="quantity" max="9" id="quantity" type="number" min="1" />}
assert_dom_equal(expected, number_field_tag("quantity", nil, :in => 1...10))
end

def test_range_input_tag
expected = %{<input name="volume" step="0.1" max="11" id="volume" type="range" min="0" />}
assert_dom_equal(expected, range_field_tag("volume", nil, :in => 0..11, :step => 0.1))
end

def test_pass
assert_equal 1, 1
end
Expand Down

9 comments on commit f8730e5

@radar
Copy link
Contributor

@radar radar commented on f8730e5 Apr 5, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You bloody ripper :)

@Aupajo
Copy link
Contributor

@Aupajo Aupajo commented on f8730e5 Apr 6, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, been looking out for this commit. :)

@stephencelis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks for handling the merge.

@dmathieu
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one :)

@carlosbrando
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like it.

@aaronchi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened to date type input?

@stephencelis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are currently a ton of date input types in the HTML5 spec: datetime, datetime-local, date, month, time, week.

The only browser that comes close to supporting them (at the time) is Opera. Till the spec is more finalized, and till more browsers support these input types, it probably makes more sense to just pass it to the :type option.

@dgm
Copy link
Contributor

@dgm dgm commented on f8730e5 Jun 4, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But with a little javascript help, other browsers are becoming supported too, such as jquerytools' dateinput tool.

@matchu
Copy link

@matchu matchu commented on f8730e5 Jun 7, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love.

Please sign in to comment.