url_helper.rb 19.8 KB
Newer Older
1
require 'action_view/helpers/javascript_helper'
2

D
Initial  
David Heinemeier Hansson 已提交
3
module ActionView
4 5 6 7
  module Helpers #:nodoc:
    # 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.
D
Initial  
David Heinemeier Hansson 已提交
8
    module UrlHelper
9 10
      include JavaScriptHelper
      
11 12 13 14 15 16 17 18
      # Returns the URL for the set of +options+ provided. This takes the 
      # same options as url_for in action controller. For a list, see the 
      # documentation for ActionController::Base#url_for. Note that it'll 
      # set :only_path => true so you'll get the relative /controller/action 
      # instead of the fully qualified http://example.com/controller/action.
      #  
      # When called from a view, url_for returns an HTML escaped url. If you 
      # need an unescaped url, pass :escape => false in the +options+.
D
Initial  
David Heinemeier Hansson 已提交
19
      def url_for(options = {}, *parameters_for_method_reference)
20 21 22 23 24 25
        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
26

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

31 32 33 34 35 36
      # Creates a link tag of the given +name+ using a URL created by the set 
      # of +options+. See the valid options in the documentation for 
      # ActionController::Base#url_for. It's also possible to pass a string instead 
      # of an options hash to get a link tag that uses the value of the string as the 
      # href for the link. If nil is passed as a name, the link itself will become 
      # the name.
37
      #
38 39
      # The +html_options+ will accept a hash of html attributes for the link tag.
      # It also accepts 3 modifiers that specialize the link behavior. 
40
      #
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
      # * <tt>:confirm => 'question?'</tt>: This will add a JavaScript confirm 
      #   prompt with the question specified. If the user accepts, the link is 
      #   processed normally, otherwise no action is taken.
      # * <tt>:popup => true || array of window options</tt>: This will force the 
      #   link to open in a popup window. By passing true, a default browser window 
      #   will be opened with the URL. You can also specify an array of options 
      #   that are passed-thru to JavaScripts window.open method.
      # * <tt>:method => symbol of HTTP verb</tt>: This modifier will dynamically
      #   create an HTML form and immediately submit the form for processing using 
      #   the HTTP verb specified. Useful for having links perform a POST operation
      #   in dangerous actions like deleting a record (which search bots can follow
      #   while spidering your site). Supported verbs are :post, :delete and :put.
      #   Note that if the user has JavaScript disabled, the request will fall back 
      #   to using GET. If you are relying on the POST behavior, your should check
      #   for it in your controllers action by using the request objects methods
      #   for post?, delete? or put?.
57
      #
58 59 60
      # You can mix and match the +html_options+ with the exception of
      # :popup and :method which will raise an ActionView::ActionViewError
      # exception.
61
      #
62
      #   link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
63
      #   link_to "Help", { :action => "help" }, :popup => true
64 65
      #   link_to "View Image", { :action => "view" }, :popup => ['new_window_name', 'height=300,width=600']
      #   link_to "Delete Image", { :action => "delete", :id => @image.id }, :confirm => "Are you sure?", :method => :delete
66
      def link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)
67 68 69 70
        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 已提交
71
        else
72
          tag_options = nil
D
Initial  
David Heinemeier Hansson 已提交
73
        end
74

75 76
        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 已提交
77 78
      end

79 80 81 82 83 84
      # Generates a form containing a single button that submits to the URL created
      # by the set of +options+. This is the safest method to ensure links that
      # cause changes to your data are not triggered by search bots or accelerators.
      # If the HTML button does not work with your layout, you can also consider
      # using the link_to method with the <tt>:method</tt> modifier as described in
      # the link_to documentation.
85
      #
86 87 88 89 90 91 92
      # The generated FORM element has a class name of <tt>button-to</tt>
      # to allow styling of the form itself and its children. You can control
      # the form submission and input element behavior using +html_options+.
      # This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers
      # described in the link_to documentation. If no <tt>:method</tt> modifier
      # is given, it will default to performing a POST operation. You can also 
      # disable the button by passing <tt>:disabled => true</tt> in +html_options+.
93
      #
94
      #   button_to "New", :action => "new"
95
      #
96
      # Generates the following HTML:
97
      #
98 99
      #   <form method="post" action="/controller/new" class="button-to">
      #     <div><input value="New" type="submit" /></div>
100 101
      #   </form>
      #
102 103
      # If you are using RESTful routes, you can pass the <tt>:method</tt>
      # to change the HTTP verb used to submit the form.
104
      #
105
      #   button_to "Delete Image", { :action => "delete", :id => @image.id },
106
      #             :confirm => "Are you sure?", :method => :delete
107
      #
108
      # Which generates the following HTML:
109
      #
110
      #   <form method="post" action="/images/delete/1" class="button-to">
111 112 113
      #     <div>
      #       <input type="hidden" name="_method" value="delete" />
      #       <input onclick="return confirm('Are you sure?');"
114
      #                 value="Delete" type="submit" />
115 116
      #     </div>
      #   </form>
117 118
      def button_to(name, options = {}, html_options = {})
        html_options = html_options.stringify_keys
119
        convert_boolean_attributes!(html_options, %w( disabled ))
120 121 122 123 124 125 126 127

        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'

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

132
        url = options.is_a?(String) ? options : self.url_for(options)
133
        name ||= url
134

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


142 143 144
      # DEPRECATED. It is reccommended to use the AssetTagHelper::image_tag within
      # a link_to method to generate a linked image.
      # 
145
      #   link_to(image_tag("rss", :size => "30x45", :border => 0), "http://www.example.com")
146
      def link_image_to(src, options = {}, html_options = {}, *parameters_for_method_reference)
147
        image_options = { "src" => src.include?("/") ? src : "/images/#{src}" }
148 149 150
        image_options["src"] += ".png" unless image_options["src"].include?(".")

        html_options = html_options.stringify_keys
151 152 153 154 155 156 157 158 159 160 161
        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
162 163 164 165 166

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

168 169 170 171 172 173 174 175
        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

176
      alias_method :link_to_image, :link_image_to
177 178
      deprecate :link_to_image => "use link_to(image_tag(...), url)",
        :link_image_to => "use link_to(image_tag(...), url)"
179

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
      # Creates a link tag of the given +name+ using a URL created by the set of
      # +options+ unless the current request uri is the same as the links, in 
      # which case only the name is returned (or the given block is yielded, if
      # one exists). Refer to the documentation for link_to_unless for block usage.
      #
      #   <ul id="navbar">
      #     <li><%= link_to_unless_current("Home", { :action => "index" }) %></li>
      #     <li><%= link_to_unless_current("About Us", { :action => "about" }) %></li>
      #   </ul>
      #
      # This will render the following HTML when on the about us page:
      #
      #   <ul id="navbar">
      #     <li><a href="/controller/index">Home</a></li>
      #     <li>About Us</li>
      #   </ul>
196 197 198 199
      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

200 201 202 203 204 205 206 207 208 209 210 211
      # Creates a link tag of the given +name+ using a URL created by the set of
      # +options+ unless +condition+ is true, in which case only the name is 
      # returned. To specialize the default behavior, you can pass a block that
      # accepts the name or the full argument list for link_to_unless (see the example).
      #
      #   <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) %>
      #
      # This example uses a block to modify the link if the condition isn't met.
      #
      #   <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name|
      #      link_to(name, { :controller => "accounts", :action => "signup" })
      #    end %>     
212 213 214 215 216
      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
217
            name
218
          end
D
Initial  
David Heinemeier Hansson 已提交
219
        else
220
          link_to(name, options, html_options, *parameters_for_method_reference)
221 222 223
        end  
      end
      
224 225 226 227 228
      # Creates a link tag of the given +name+ using a URL created by the set of
      # +options+ if +condition+ is true, in which case only the name is 
      # returned. To specialize the default behavior, you can pass a block that
      # accepts the name or the full argument list for link_to_unless (see the examples
      # in link_to_unless).
229 230
      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 已提交
231 232
      end

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
      # Creates a mailto link tag to the specified +email_address+, which is
      # also used as the name of the link unless +name+ is specified. Additional
      # html attributes for the link can be passed in +html_options+.
      #
      # mail_to has several methods for hindering email harvestors and customizing
      # the email itself by passing special keys to +html_options+.
      #
      # Special HTML Options:
      #
      # * <tt>:encode</tt>  - This key will accept the strings "javascript" or "hex".
      #   Passing "javascript" will dynamically create and encode the mailto: link then
      #   eval it into the DOM of the page. This method will not show the link on
      #   the page if the user has JavaScript disabled. Passing "hex" will hex
      #   encode the +email_address+ before outputting the mailto: link.
      # * <tt>:replace_at</tt>  - When the link +name+ isn't provided, the
      #   +email_address+ is used for the link label. You can use this option to
      #   obfuscate the +email_address+ by substituting the @ sign with the string
      #   given as the value.
      # * <tt>:replace_dot</tt>  - When the link +name+ isn't provided, the
      #   +email_address+ is used for the link label. You can use this option to
      #   obfuscate the +email_address+ by substituting the . in the email with the
      #   string given as the value.
      # * <tt>:subject</tt>  - Preset the subject line of the email.
      # * <tt>:body</tt> - Preset the body of the email.
      # * <tt>:cc</tt>  - Carbon Copy addition recipients on the email.
      # * <tt>:bcc</tt>  - Blind Carbon Copy additional recipients on the email.
259
      #
260
      # Examples:
261
      #   mail_to "me@domain.com"  # => <a href="mailto:me@domain.com">me@domain.com</a>
262
      #   mail_to "me@domain.com", "My email", :encode => "javascript"  # =>
263
      #     <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
264 265 266
      #
      #   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>
267
      #
268 269 270 271 272 273
      #   mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"  # =>
      #     <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
      #
      #   mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", 
      #            :subject => "This is an example email"  # =>
      #     <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
D
Initial  
David Heinemeier Hansson 已提交
274
      def mail_to(email_address, name = nil, html_options = {})
275
        html_options = html_options.stringify_keys
276
        encode = html_options.delete("encode").to_s
277 278
        cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")

279
        string = ''
280 281 282 283 284 285 286
        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?

287 288
        email_address = email_address.to_s

289 290 291 292
        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")

293 294
        if encode == "javascript"
          tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');"
295 296 297
          for i in 0...tmp.length
            string << sprintf("%%%x",tmp[i])
          end
298
          "<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
299
        elsif encode == "hex"
300 301 302 303 304 305 306 307
          email_address_encoded = ''
          email_address_obfuscated.each_byte do |c|
            email_address_encoded << sprintf("&#%d;", c)
          end

          protocol = 'mailto:'
          protocol.each_byte { |c| string << sprintf("&#%d;", c) }

308 309 310 311 312 313 314
          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
315
          content_tag "a", name || email_address_encoded, html_options.merge({ "href" => "#{string}#{extras}" })
316
        else
317
          content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
318
        end
D
Initial  
David Heinemeier Hansson 已提交
319 320
      end

321
      # True if the current request uri was generated by the given +options+.
322
      def current_page?(options)
323 324 325 326 327 328 329
        url_string = CGI.escapeHTML(url_for(options))
        request = @controller.request
        if url_string =~ /^\w+:\/\//
          url_string == "#{request.protocol}#{request.host_with_port}#{request.request_uri}"
        else
          url_string == request.request_uri
        end
330 331
      end

D
Initial  
David Heinemeier Hansson 已提交
332
      private
333
        def convert_options_to_javascript!(html_options)
334 335 336 337
          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")
338 339 340 341 342 343 344
          if !method && post
            ActiveSupport::Deprecation.warn(
              "Passing :post as a link modifier is deprecated. " +
              "Use :method => \"post\" instead. :post will be removed in Rails 2.0."
            )
            method = :post
          end
345 346
        
          html_options["onclick"] = case
347
            when popup && method
348 349 350
              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;"
351 352
            when confirm && method
              "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
353 354
            when confirm
              "return #{confirm_javascript_function(confirm)};"
355 356
            when method
              "#{method_javascript_function(method)}return false;"
357 358
            when popup
              popup_javascript_function(popup) + 'return false;'
359 360
            else
              html_options["onclick"]
D
Initial  
David Heinemeier Hansson 已提交
361 362
          end
        end
363
        
364 365
        def confirm_javascript_function(confirm)
          "confirm('#{escape_javascript(confirm)}')"
366
        end
367 368 369
        
        def popup_javascript_function(popup)
          popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
370 371
        end
        
372 373 374 375 376 377 378 379 380 381 382
        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();"
383 384
        end

385 386 387 388 389 390 391 392 393 394
        # 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 已提交
395
        # if the associated _bool_value_ evaluates to true, it is
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
        # 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 已提交
412 413
    end
  end
414
end