Skip to content
This repository
Browse code

Performance: javascript helper tweaks to speed up escaping and reduce…

… object allocations when building options strings
  • Loading branch information...
commit f4ccc179530d5b9436da87d3c221dfa8fa89119a 1 parent 9a0e443
Jeremy Kemper authored June 21, 2008
129  actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -4,10 +4,10 @@
4 4
 module ActionView
5 5
   module Helpers
6 6
     # Provides functionality for working with JavaScript in your views.
7  
-    # 
  7
+    #
8 8
     # == Ajax, controls and visual effects
9  
-    # 
10  
-    # * For information on using Ajax, see 
  9
+    #
  10
+    # * For information on using Ajax, see
11 11
     #   ActionView::Helpers::PrototypeHelper.
12 12
     # * For information on using controls and visual effects, see
13 13
     #   ActionView::Helpers::ScriptaculousHelper.
@@ -20,22 +20,22 @@ module Helpers
20 20
     # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
21 21
     # following:
22 22
     #
23  
-    # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD 
24  
-    #   section of your page (recommended): This function will return 
  23
+    # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
  24
+    #   section of your page (recommended): This function will return
25 25
     #   references to the JavaScript files created by the +rails+ command in
26 26
     #   your <tt>public/javascripts</tt> directory. Using it is recommended as
27  
-    #   the browser can then cache the libraries instead of fetching all the 
  27
+    #   the browser can then cache the libraries instead of fetching all the
28 28
     #   functions anew on every request.
29  
-    # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but 
  29
+    # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
30 30
     #   will only include the Prototype core library, which means you are able
31  
-    #   to use all basic AJAX functionality. For the Scriptaculous-based 
32  
-    #   JavaScript helpers, like visual effects, autocompletion, drag and drop 
  31
+    #   to use all basic AJAX functionality. For the Scriptaculous-based
  32
+    #   JavaScript helpers, like visual effects, autocompletion, drag and drop
33 33
     #   and so on, you should use the method described above.
34 34
     # * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
35 35
     #   JavaScript support functions within a single script block. Not
36 36
     #   recommended.
37 37
     #
38  
-    # For documentation on +javascript_include_tag+ see 
  38
+    # For documentation on +javascript_include_tag+ see
39 39
     # ActionView::Helpers::AssetTagHelper.
40 40
     module JavaScriptHelper
41 41
       unless const_defined? :JAVASCRIPT_PATH
@@ -43,13 +43,13 @@ module JavaScriptHelper
43 43
       end
44 44
 
45 45
       include PrototypeHelper
46  
-      
47  
-      # Returns a link that will trigger a JavaScript +function+ using the 
  46
+
  47
+      # Returns a link that will trigger a JavaScript +function+ using the
48 48
       # onclick handler and return false after the fact.
49 49
       #
50 50
       # The +function+ argument can be omitted in favor of an +update_page+
51 51
       # block, which evaluates to a string when the template is rendered
52  
-      # (instead of making an Ajax request first).      
  52
+      # (instead of making an Ajax request first).
53 53
       #
54 54
       # Examples:
55 55
       #   link_to_function "Greeting", "alert('Hello world!')"
@@ -70,36 +70,31 @@ module JavaScriptHelper
70 70
       #       <a href="#" id="more_link" onclick="try {
71 71
       #         $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
72 72
       #         $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
73  
-      #       } 
74  
-      #       catch (e) { 
75  
-      #         alert('RJS error:\n\n' + e.toString()); 
  73
+      #       }
  74
+      #       catch (e) {
  75
+      #         alert('RJS error:\n\n' + e.toString());
76 76
       #         alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
77 77
       #         \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
78  
-      #         throw e 
  78
+      #         throw e
79 79
       #       };
80 80
       #       return false;">Show me more</a>
81 81
       #
82 82
       def link_to_function(name, *args, &block)
83  
-        html_options = args.extract_options!
84  
-        function = args[0] || ''
85  
-
86  
-        html_options.symbolize_keys!
87  
-        function = update_page(&block) if block_given?
88  
-        content_tag(
89  
-          "a", name, 
90  
-          html_options.merge({ 
91  
-            :href => html_options[:href] || "#", 
92  
-            :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;" 
93  
-          })
94  
-        )
  83
+        html_options = args.extract_options!.symbolize_keys!
  84
+
  85
+        function = block_given? ? update_page(&block) : args[0] || ''
  86
+        onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
  87
+        href = html_options[:href] || '#'
  88
+
  89
+        content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
95 90
       end
96  
-      
97  
-      # Returns a button that'll trigger a JavaScript +function+ using the 
  91
+
  92
+      # Returns a button that'll trigger a JavaScript +function+ using the
98 93
       # onclick handler.
99 94
       #
100 95
       # The +function+ argument can be omitted in favor of an +update_page+
101 96
       # block, which evaluates to a string when the template is rendered
102  
-      # (instead of making an Ajax request first).      
  97
+      # (instead of making an Ajax request first).
103 98
       #
104 99
       # Examples:
105 100
       #   button_to_function "Greeting", "alert('Hello world!')"
@@ -111,45 +106,56 @@ def link_to_function(name, *args, &block)
111 106
       #     page[:details].visual_effect :toggle_slide
112 107
       #   end
113 108
       def button_to_function(name, *args, &block)
114  
-        html_options = args.extract_options!
115  
-        function = args[0] || ''
116  
-
117  
-        html_options.symbolize_keys!
118  
-        function = update_page(&block) if block_given?
119  
-        tag(:input, html_options.merge({ 
120  
-          :type => "button", :value => name, 
121  
-          :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" 
122  
-        }))
  109
+        html_options = args.extract_options!.symbolize_keys!
  110
+
  111
+        function = block_given? ? update_page(&block) : args[0] || ''
  112
+        onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
  113
+
  114
+        tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
123 115
       end
124 116
 
125  
-      # Includes the Action Pack JavaScript libraries inside a single <script> 
  117
+      # Includes the Action Pack JavaScript libraries inside a single <script>
126 118
       # tag. The function first includes prototype.js and then its core extensions,
127 119
       # (determined by filenames starting with "prototype").
128 120
       # Afterwards, any additional scripts will be included in undefined order.
129 121
       #
130 122
       # Note: The recommended approach is to copy the contents of
131 123
       # lib/action_view/helpers/javascripts/ into your application's
132  
-      # public/javascripts/ directory, and use +javascript_include_tag+ to 
  124
+      # public/javascripts/ directory, and use +javascript_include_tag+ to
133 125
       # create remote <script> links.
134 126
       def define_javascript_functions
135 127
         javascript = "<script type=\"#{Mime::JS}\">"
136  
-        
137  
-        # load prototype.js and its extensions first 
  128
+
  129
+        # load prototype.js and its extensions first
138 130
         prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
139  
-        prototype_libs.each do |filename| 
  131
+        prototype_libs.each do |filename|
140 132
           javascript << "\n" << IO.read(filename)
141 133
         end
142  
-        
  134
+
143 135
         # load other libraries
144  
-        (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename| 
  136
+        (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
145 137
           javascript << "\n" << IO.read(filename)
146 138
         end
147 139
         javascript << '</script>'
148 140
       end
149 141
 
  142
+
  143
+      JS_ESCAPE_MAP = {
  144
+        '\\'    => '\\\\',
  145
+        '</'    => '<\/',
  146
+        "\r\n"  => '\n',
  147
+        "\n"    => '\n',
  148
+        "\r"    => '\n',
  149
+        '"'     => '\\"',
  150
+        "'"     => "\\'" }
  151
+
150 152
       # Escape carrier returns and single and double quotes for JavaScript segments.
151 153
       def escape_javascript(javascript)
152  
-        (javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
  154
+        if javascript
  155
+          javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
  156
+        else
  157
+          ''
  158
+        end
153 159
       end
154 160
 
155 161
       # Returns a JavaScript tag with the +content+ inside. Example:
@@ -163,7 +169,7 @@ def escape_javascript(javascript)
163 169
       #   </script>
164 170
       #
165 171
       # +html_options+ may be a hash of attributes for the <script> tag. Example:
166  
-      #   javascript_tag "alert('All is good')", :defer => 'defer' 
  172
+      #   javascript_tag "alert('All is good')", :defer => 'defer'
167 173
       #   # => <script defer="defer" type="text/javascript">alert('All is good')</script>
168 174
       #
169 175
       # Instead of passing the content as an argument, you can also use a block
@@ -180,30 +186,37 @@ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &bloc
180 186
             content_or_options_with_block
181 187
           end
182 188
 
183  
-        tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
  189
+        tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
184 190
 
185  
-        block_given? ? concat(tag) : tag
  191
+        if block_called_from_erb?(block)
  192
+          concat(tag)
  193
+        else
  194
+          tag
  195
+        end
186 196
       end
187 197
 
188 198
       def javascript_cdata_section(content) #:nodoc:
189 199
         "\n//#{cdata_section("\n#{content}\n//")}\n"
190 200
       end
191  
-      
  201
+
192 202
     protected
193 203
       def options_for_javascript(options)
194  
-        '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
  204
+        if options.empty?
  205
+          '{}'
  206
+        else
  207
+          "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
  208
+        end
195 209
       end
196  
-      
  210
+
197 211
       def array_or_string_for_javascript(option)
198  
-        js_option = if option.kind_of?(Array)
  212
+        if option.kind_of?(Array)
199 213
           "['#{option.join('\',\'')}']"
200 214
         elsif !option.nil?
201 215
           "'#{option}'"
202 216
         end
203  
-        js_option
204 217
       end
205 218
     end
206  
-    
  219
+
207 220
     JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
208 221
   end
209 222
 end
2  actionpack/lib/action_view/helpers/tag_helper.rb
@@ -115,7 +115,7 @@ def escape_once(html)
115 115
         # can't take an <% end %> later on, so we have to use <% ... %>
116 116
         # and implicitly concat.
117 117
         def block_called_from_erb?(block)
118  
-          eval(BLOCK_CALLED_FROM_ERB, block)
  118
+          block && eval(BLOCK_CALLED_FROM_ERB, block)
119 119
         end
120 120
 
121 121
         def content_tag_string(name, content, options, escape = true)
2  actionpack/lib/action_view/helpers/url_helper.rb
@@ -535,7 +535,7 @@ def convert_options_to_javascript!(html_options, url = '')
535 535
             when method
536 536
               "#{method_javascript_function(method, url, href)}return false;"
537 537
             when popup
538  
-              popup_javascript_function(popup) + 'return false;'
  538
+              "#{popup_javascript_function(popup)}return false;"
539 539
             else
540 540
               html_options["onclick"]
541 541
           end

0 notes on commit f4ccc17

Please sign in to comment.
Something went wrong with that request. Please try again.