From 7e3dc807df2193e7bb06b7125d0420d1b05278db Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 7 Sep 2005 12:56:33 +0000 Subject: [PATCH] Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2149 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 2 + .../lib/action_view/helpers/url_helper.rb | 64 ++++++++++++------- actionpack/test/template/url_helper_test.rb | 18 ++++++ 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index ae4da0c2f3..bb721d40dc 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript + * Fixed overwrite_params * Added ActionController::Base.benchmark and ActionController::Base.silence to allow for easy benchmarking and turning off the log diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 64288cb4bd..0b84b3311b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -20,16 +20,22 @@ module ActionView # link:classes/ActionController/Base.html#M000021. It's also possible to pass a string instead of an options hash to # get a link tag that just points without consideration. If nil is passed as a name, the link itself will become the name. # - # The html_options has two special features. One for creating javascript confirm alerts where if you pass :confirm => 'Are you sure?', + # The html_options has three special features. One for creating javascript confirm alerts where if you pass :confirm => 'Are you sure?', # the link will be guarded with a JS popup asking that question. If the user accepts, the link is processed, otherwise not. # - # The other for creating a popup window, which is done by either passing :popup with true or the options of the window in + # Another for creating a popup window, which is done by either passing :popup with true or the options of the window in # Javascript form. # + # And a third for making the link do a POST request (instead of the regular GET) through a dynamically added form element that + # is instantly submitted. Note that if the user has turned off Javascript, the request will fall back on the GET. So its + # your responsibility to determine what the action should be once it arrives at the controller. The POST form is turned on by + # passing :post as true. Note, it's not possible to use POST requests and popup targets at the same time (an exception will be thrown). + # # Examples: # link_to "Delete this page", { :action => "destroy", :id => @page.id }, :confirm => "Are you sure?" # link_to "Help", { :action => "help" }, :popup => true # link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600'] + # link_to "Destroy account", { :action => "destroy" }, :confirm => "Are you sure?", :post => true def link_to(name, options = {}, html_options = nil, *parameters_for_method_reference) html_options = (html_options || {}).stringify_keys convert_options_to_javascript!(html_options) @@ -90,15 +96,20 @@ module ActionView # +td+ element or in between +p+ elements, but not in the middle of # a run of text, nor can you place a form within another form. # (Bottom line: Always validate your HTML before going public.) - def button_to(name, options = {}, html_options = nil) html_options = (html_options || {}).stringify_keys convert_boolean_attributes!(html_options, %w( disabled )) - convert_confirm_option_to_javascript!(html_options) - url, name = options.is_a?(String) ? + + if confirm = html_options.delete("confirm") + html_options["onclick"] = "return #{confirm_javascript_function(confirm)};" + end + + url, name = options.is_a?(String) ? [ options, name || options ] : [ url_for(options), name || url_for(options) ] + html_options.merge!("type" => "submit", "value" => name) + "
" + tag("input", html_options) + "
" end @@ -227,26 +238,37 @@ module ActionView end private - def convert_confirm_option_to_javascript!(html_options) - if confirm = html_options.delete("confirm") - html_options["onclick"] = "return confirm('#{escape_javascript(confirm)}');" - return confirm + def convert_options_to_javascript!(html_options) + confirm, popup, post = html_options.delete("confirm"), html_options.delete("popup"), html_options.delete("post") + + html_options["onclick"] = case + when popup && post + raise ActionView::ActionViewError, "You can't use :popup and :post in the same link" + when confirm && popup + "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;" + when confirm && post + "if (#{confirm_javascript_function(confirm)}) { #{post_javascript_function} };return false;" + when confirm + "return #{confirm_javascript_function(confirm)};" + when post + "#{post_javascript_function}return false;" + when popup + popup_javascript_function(popup) + 'return false;' end end - def convert_popup_option_to_javascript!(html_options, confirm_message = false) - if popup = html_options.delete("popup") - popup_js = popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);" - html_options["onclick"] = popup_js + 'return false;' unless confirm_message - html_options["onclick"] = "if (confirm('#{escape_javascript(confirm_message)}')) { #{popup_js} };return false;" if confirm_message - end - end - - def convert_options_to_javascript!(html_options) - confirm_message = convert_confirm_option_to_javascript!(html_options) - convert_popup_option_to_javascript!(html_options, confirm_message) + def confirm_javascript_function(confirm) + "confirm('#{escape_javascript(confirm)}')" end + def popup_javascript_function(popup) + popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);" + end + + def post_javascript_function + "f = document.createElement('form'); document.body.appendChild(f); f.method = 'POST'; f.action = url; f.submit();" + end + # Processes the _html_options_ hash, converting the boolean # attributes from true/false form into the form required by # HTML/XHTML. (An attribute is considered to be boolean if @@ -270,12 +292,10 @@ module ActionView # # convert_boolean_attributes!( html_options, # %w( checked disabled readonly ) ) - def convert_boolean_attributes!(html_options, bool_attrs) bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) } html_options end - end end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 73d87f6956..185f455777 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -88,6 +88,24 @@ class UrlHelperTest < Test::Unit::TestCase ) end + def test_link_tag_using_post_javascript + assert_equal( + "Hello", + link_to("Hello", "http://www.example.com", :post => true) + ) + end + + def test_link_tag_using_post_javascript_and_confirm + assert_equal( + "Hello", + link_to("Hello", "http://www.example.com", :post => true, :confirm => "Are you serious?") + ) + end + + def test_link_tag_using_post_javascript_and_popup + assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :post => true, :confirm => "Are you serious?") } + end + def test_link_to_unless assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog") assert "Listing", link_to_unless(false, "Listing", :action => "list", :controller => "weblog")