url_helper.rb 17.0 KB
Newer Older
1 2
require File.dirname(__FILE__) + '/javascript_helper'

D
Initial  
David Heinemeier Hansson 已提交
3 4 5 6 7 8 9
module ActionView
  module Helpers
    # Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
    # you can use the same format for links in the views that you do in the controller. The different methods are even named
    # synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
    # redirection in redirect_to.
    module UrlHelper
10 11
      include JavaScriptHelper
      
12
      # Returns the URL for the set of +options+ provided. This takes the same options 
13
      # as url_for. For a list, see the documentation for ActionController::Base#url_for.
D
David Heinemeier Hansson 已提交
14 15
      # Note that it'll set :only_path => true so you'll get /controller/action instead of the 
      # http://example.com/controller/action part (makes it harder to parse httpd log files)
16 17 18 19
      # 
      # When called from a view, url_for returns an HTML escaped url. If you need an unescaped
      # url, pass :escape => false to url_for.
      # 
D
Initial  
David Heinemeier Hansson 已提交
20
      def url_for(options = {}, *parameters_for_method_reference)
21 22 23 24 25 26
        if options.kind_of? Hash
          options = { :only_path => true }.update(options.symbolize_keys)
          escape = options.key?(:escape) ? options.delete(:escape) : true
        else
          escape = true
        end
27

28 29
        url = @controller.send(:url_for, options, *parameters_for_method_reference)
        escape ? html_escape(url) : url
D
Initial  
David Heinemeier Hansson 已提交
30 31 32
      end

      # Creates a link tag of the given +name+ using an URL created by the set of +options+. See the valid options in
33
      # the documentation for ActionController::Base#url_for. It's also possible to pass a string instead of an options hash to
34
      # get a link tag that just points without consideration. If nil is passed as a name, the link itself will become the name.
35
      #
36
      # The html_options has three special features. One for creating javascript confirm alerts where if you pass :confirm => 'Are you sure?',
37
      # the link will be guarded with a JS popup asking that question. If the user accepts, the link is processed, otherwise not.
38
      #
39
      # Another for creating a popup window, which is done by either passing :popup with true or the options of the window in 
40 41
      # Javascript form.
      #
42 43 44 45 46
      # And a third for making the link do a non-GET request 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 form is turned on by
      # passing :method with the option of either :post, :delete, or :put as the value. Usually only :post or :delete will make sense, though.
      # Note, it's not possible to use method request and popup targets at the same time (an exception will be thrown).
47
      #
48
      # Examples:
49
      #   link_to "Delete this page", { :action => "destroy", :id => @page.id }, :confirm => "Are you sure?"
50 51
      #   link_to "Help", { :action => "help" }, :popup => true
      #   link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
52
      #   link_to "Destroy account", { :action => "destroy" }, :confirm => "Are you sure?", :method => :delete
53
      def link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)
54 55 56 57
        if html_options
          html_options = html_options.stringify_keys
          convert_options_to_javascript!(html_options)
          tag_options = tag_options(html_options)
D
Initial  
David Heinemeier Hansson 已提交
58
        else
59
          tag_options = nil
D
Initial  
David Heinemeier Hansson 已提交
60
        end
61

62 63
        url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
        "<a href=\"#{url}\"#{tag_options}>#{name || url}</a>"
D
Initial  
David Heinemeier Hansson 已提交
64 65
      end

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
      # Generates a form containing a sole button that submits to the
      # URL given by _options_.  Use this method instead of +link_to+
      # for actions that do not have the safe HTTP GET semantics
      # implied by using a hypertext link.
      #
      # The parameters are the same as for +link_to+.  Any _html_options_
      # that you pass will be applied to the inner +input+ element.
      # In particular, pass
      # 
      #   :disabled => true/false
      #
      # as part of _html_options_ to control whether the button is
      # disabled.  The generated form element is given the class
      # 'button-to', to which you can attach CSS styles for display
      # purposes.
      #
      # Example 1:
      #
      #   # inside of controller for "feeds"
      #   button_to "Edit", :action => 'edit', :id => 3
      #
      # Generates the following HTML (sans formatting):
      #
      #   <form method="post" action="/feeds/edit/3" class="button-to">
      #     <div><input value="Edit" type="submit" /></div>
      #   </form>
      #
      # Example 2:
      #
      #   button_to "Destroy", { :action => 'destroy', :id => 3 },
96
      #             :confirm => "Are you sure?", :method => :delete
97 98 99 100
      #
      # Generates the following HTML (sans formatting):
      #
      #   <form method="post" action="/feeds/destroy/3" class="button-to">
101 102 103
      #     <div>
      #       <input type="hidden" name="_method" value="delete" />
      #       <input onclick="return confirm('Are you sure?');"
104 105 106 107 108 109 110 111 112 113 114 115 116 117
      #                 value="Destroy" type="submit" />
      #     </div>
      #   </form>
      #
      # *NOTE*: This method generates HTML code that represents a form.
      # Forms are "block" content, which means that you should not try to
      # insert them into your HTML where only inline content is expected.
      # For example, you can legally insert a form inside of a +div+ or
      # +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 ))
118 119 120 121 122 123 124 125

        method_tag = ''
        if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
          method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
        end

        form_method = method.to_s == 'get' ? 'get' : 'post'

126 127 128
        if confirm = html_options.delete("confirm")
          html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
        end
129

130 131
        url = options.is_a?(String) ? options : url_for(options)
        name ||= url
132

133
        html_options.merge!("type" => "submit", "value" => name)
134 135
        
        "<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" + 
136
          method_tag + tag("input", html_options) + "</div></form>"
137 138 139
      end


140
      # This tag is deprecated. Combine the link_to and AssetTagHelper::image_tag yourself instead, like:
141
      #   link_to(image_tag("rss", :size => "30x45", :border => 0), "http://www.example.com")
142
      def link_image_to(src, options = {}, html_options = {}, *parameters_for_method_reference)
143
        image_options = { "src" => src.include?("/") ? src : "/images/#{src}" }
144 145 146
        image_options["src"] += ".png" unless image_options["src"].include?(".")

        html_options = html_options.stringify_keys
147 148 149 150 151 152 153 154 155 156 157
        if html_options["alt"]
          image_options["alt"] = html_options["alt"]
          html_options.delete "alt"
        else
          image_options["alt"] = src.split("/").last.split(".").first.capitalize
        end

        if html_options["size"]
          image_options["width"], image_options["height"] = html_options["size"].split("x")
          html_options.delete "size"
        end
158 159 160 161 162

        if html_options["border"]
          image_options["border"] = html_options["border"]
          html_options.delete "border"
        end
163

164 165 166 167 168 169 170 171
        if html_options["align"]
          image_options["align"] = html_options["align"]
          html_options.delete "align"
        end

        link_to(tag("img", image_options), options, html_options, *parameters_for_method_reference)
      end

172 173
      alias_method :link_to_image, :link_image_to # deprecated name

174
      # Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
175
      # request uri is the same as the link's, in which case only the name is returned (or the
176
      # given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
D
Initial  
David Heinemeier Hansson 已提交
177
      # to the page currently being viewed.
178 179 180 181 182 183 184 185 186 187 188
      def link_to_unless_current(name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
        link_to_unless current_page?(options), name, options, html_options, *parameters_for_method_reference, &block
      end

      # Create a link tag of the given +name+ using an URL created by the set of +options+, unless +condition+
      # is true, in which case only the name is returned (or the given block is yielded, if one exists). 
      def link_to_unless(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
        if condition
          if block_given?
            block.arity <= 1 ? yield(name) : yield(name, options, html_options, *parameters_for_method_reference)
          else
189
            name
190
          end
D
Initial  
David Heinemeier Hansson 已提交
191
        else
192
          link_to(name, options, html_options, *parameters_for_method_reference)
193 194 195 196 197 198 199
        end  
      end
      
      # Create a link tag of the given +name+ using an URL created by the set of +options+, if +condition+
      # is true, in which case only the name is returned (or the given block is yielded, if one exists). 
      def link_to_if(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
        link_to_unless !condition, name, options, html_options, *parameters_for_method_reference, &block
D
Initial  
David Heinemeier Hansson 已提交
200 201 202 203
      end

      # Creates a link tag for starting an email to the specified <tt>email_address</tt>, which is also used as the name of the
      # link unless +name+ is specified. Additional HTML options, such as class or id, can be passed in the <tt>html_options</tt> hash.
204 205
      #
      # You can also make it difficult for spiders to harvest email address by obfuscating them.
206
      # Examples:
207 208 209 210 211
      #   mail_to "me@domain.com", "My email", :encode => "javascript"  # =>
      #     <script type="text/javascript" language="javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>
      #
      #   mail_to "me@domain.com", "My email", :encode => "hex"  # =>
      #     <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
212 213 214 215 216 217 218 219
      #
      # You can also specify the cc address, bcc address, subject, and body parts of the message header to create a complex e-mail using the
      # corresponding +cc+, +bcc+, +subject+, and +body+ <tt>html_options</tt> keys. Each of these options are URI escaped and then appended to
      # the <tt>email_address</tt> before being output. <b>Be aware that javascript keywords will not be escaped and may break this feature
      # when encoding with javascript.</b>
      # Examples:
      #   mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", :bcc => "bccaddress@domain.com", :subject => "This is an example email", :body => "This is the body of the message."   # =>
      #     <a href="mailto:me@domain.com?cc="ccaddress@domain.com"&bcc="bccaddress@domain.com"&body="This%20is%20the%20body%20of%20the%20message."&subject="This%20is%20an%20example%20email">My email</a>
D
Initial  
David Heinemeier Hansson 已提交
220
      def mail_to(email_address, name = nil, html_options = {})
221 222
        html_options = html_options.stringify_keys
        encode = html_options.delete("encode")
223 224
        cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")

225
        string = ''
226 227 228 229 230 231 232
        extras = ''
        extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
        extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
        extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
        extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
        extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?

233 234 235 236
        email_address_obfuscated = email_address.dup
        email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
        email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")

237
        if encode == 'javascript'
238
          tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address.to_s+extras }))}');"
239 240 241
          for i in 0...tmp.length
            string << sprintf("%%%x",tmp[i])
          end
242
          "<script type=\"text/javascript\">eval(unescape('#{string}'))</script>"
243 244 245 246 247 248 249 250
        elsif encode == 'hex'
          for i in 0...email_address.length
            if email_address[i,1] =~ /\w/
              string << sprintf("%%%x",email_address[i])
            else
              string << email_address[i,1]
            end
          end
251
          content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{string}#{extras}" })
252
        else
253
          content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
254
        end
D
Initial  
David Heinemeier Hansson 已提交
255 256
      end

257 258
      # Returns true if the current page uri is generated by the options passed (in url_for format).
      def current_page?(options)
259
        CGI.escapeHTML(url_for(options)) == @controller.request.request_uri
260 261
      end

D
Initial  
David Heinemeier Hansson 已提交
262
      private
263
        def convert_options_to_javascript!(html_options)
264 265 266 267 268
          confirm, popup = html_options.delete("confirm"), html_options.delete("popup")

          # post is deprecated, but if its specified and method is not, assume that method = :post
          method, post   = html_options.delete("method"), html_options.delete("post")
          method = :post if !method && post
269 270
        
          html_options["onclick"] = case
271
            when popup && method
272 273 274
              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;"
275 276
            when confirm && method
              "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
277 278
            when confirm
              "return #{confirm_javascript_function(confirm)};"
279 280
            when method
              "#{method_javascript_function(method)}return false;"
281 282
            when popup
              popup_javascript_function(popup) + 'return false;'
283 284
            else
              html_options["onclick"]
D
Initial  
David Heinemeier Hansson 已提交
285 286
          end
        end
287
        
288 289
        def confirm_javascript_function(confirm)
          "confirm('#{escape_javascript(confirm)}')"
290
        end
291 292 293
        
        def popup_javascript_function(popup)
          popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
294 295
        end
        
296 297 298 299 300 301 302 303 304 305 306
        def method_javascript_function(method)
          submit_function = 
            "var f = document.createElement('form'); f.style.display = 'none'; " +
            "this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;"
          
          unless method == :post
            submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
            submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);"
          end
          
          submit_function << "f.submit();"
307 308
        end

309 310 311 312 313 314 315 316 317 318
        # 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
        # its name is listed in the given _bool_attrs_ array.)
        #
        # More specifically, for each boolean attribute in _html_options_
        # given as:
        #
        #     "attr" => bool_value
        #
D
David Heinemeier Hansson 已提交
319
        # if the associated _bool_value_ evaluates to true, it is
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
        # replaced with the attribute's name; otherwise the attribute is
        # removed from the _html_options_ hash.  (See the XHTML 1.0 spec,
        # section 4.5 "Attribute Minimization" for more:
        # http://www.w3.org/TR/xhtml1/#h-4.5)
        #
        # Returns the updated _html_options_ hash, which is also modified
        # in place.
        #
        # Example:
        #
        #   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
D
Initial  
David Heinemeier Hansson 已提交
336 337
    end
  end
338
end