halorgium-actionpack 3.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. metadata +236 -0
@@ -0,0 +1,1309 @@
1
+ require 'set'
2
+ require 'active_support/json'
3
+ require 'active_support/core_ext/object/extending'
4
+ require 'active_support/core_ext/object/returning'
5
+
6
+ module ActionView
7
+ module Helpers
8
+ # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
9
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
10
+ # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
11
+ # functionality, and more traditional object-oriented facilities for JavaScript.
12
+ # This module provides a set of helpers to make it more convenient to call
13
+ # functions from Prototype using Rails, including functionality to call remote
14
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
15
+ # This means that you can call actions in your controllers without
16
+ # reloading the page, but still update certain parts of it using
17
+ # injections into the DOM. A common use case is having a form that adds
18
+ # a new element to a list without reloading the page or updating a shopping
19
+ # cart total when a new item is added.
20
+ #
21
+ # == Usage
22
+ # To be able to use these helpers, you must first include the Prototype
23
+ # JavaScript framework in your pages.
24
+ #
25
+ # javascript_include_tag 'prototype'
26
+ #
27
+ # (See the documentation for
28
+ # ActionView::Helpers::JavaScriptHelper for more information on including
29
+ # this and other JavaScript files in your Rails templates.)
30
+ #
31
+ # Now you're ready to call a remote action either through a link...
32
+ #
33
+ # link_to_remote "Add to cart",
34
+ # :url => { :action => "add", :id => product.id },
35
+ # :update => { :success => "cart", :failure => "error" }
36
+ #
37
+ # ...through a form...
38
+ #
39
+ # <% form_remote_tag :url => '/shipping' do -%>
40
+ # <div><%= submit_tag 'Recalculate Shipping' %></div>
41
+ # <% end -%>
42
+ #
43
+ # ...periodically...
44
+ #
45
+ # periodically_call_remote(:url => 'update', :frequency => '5', :update => 'ticker')
46
+ #
47
+ # ...or through an observer (i.e., a form or field that is observed and calls a remote
48
+ # action when changed).
49
+ #
50
+ # <%= observe_field(:searchbox,
51
+ # :url => { :action => :live_search }),
52
+ # :frequency => 0.5,
53
+ # :update => :hits,
54
+ # :with => 'query'
55
+ # %>
56
+ #
57
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
58
+ # are listed here); check out the documentation for each method to find out more about its usage and options.
59
+ #
60
+ # === Common Options
61
+ # See link_to_remote for documentation of options common to all Ajax
62
+ # helpers; any of the options specified by link_to_remote can be used
63
+ # by the other helpers.
64
+ #
65
+ # == Designing your Rails actions for Ajax
66
+ # When building your action handlers (that is, the Rails actions that receive your background requests), it's
67
+ # important to remember a few things. First, whatever your action would normally return to the browser, it will
68
+ # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
69
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
70
+ # You can turn the layout off on particular actions by doing the following:
71
+ #
72
+ # class SiteController < ActionController::Base
73
+ # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
74
+ # end
75
+ #
76
+ # Optionally, you could do this in the method you wish to lack a layout:
77
+ #
78
+ # render :layout => false
79
+ #
80
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
81
+ # method that Ajax uses to make background requests) method.
82
+ # def name
83
+ # # Is this an XmlHttpRequest request?
84
+ # if (request.xhr?)
85
+ # render :text => @name.to_s
86
+ # else
87
+ # # No? Then render an action.
88
+ # render :action => 'view_attribute', :attr => @name
89
+ # end
90
+ # end
91
+ #
92
+ # The else clause can be left off and the current action will render with full layout and template. An extension
93
+ # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
94
+ #
95
+ # layout proc{ |c| c.request.xhr? ? false : "application" }
96
+ #
97
+ # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
98
+ #
99
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
100
+ # render text output, like this:
101
+ #
102
+ # render :text => 'Return this from my method!'
103
+ #
104
+ # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
105
+ # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
106
+ #
107
+ # == Updating multiple elements
108
+ # See JavaScriptGenerator for information on updating multiple elements
109
+ # on the page in an Ajax response.
110
+ module PrototypeHelper
111
+ unless const_defined? :CALLBACKS
112
+ CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
113
+ :interactive, :complete, :failure, :success ] +
114
+ (100..599).to_a)
115
+ AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
116
+ :asynchronous, :method, :insertion, :position,
117
+ :form, :with, :update, :script, :type ]).merge(CALLBACKS)
118
+ end
119
+
120
+ # Returns a link to a remote action defined by <tt>options[:url]</tt>
121
+ # (using the url_for format) that's called in the background using
122
+ # XMLHttpRequest. The result of that request can then be inserted into a
123
+ # DOM object whose id can be specified with <tt>options[:update]</tt>.
124
+ # Usually, the result would be a partial prepared by the controller with
125
+ # render :partial.
126
+ #
127
+ # Examples:
128
+ # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
129
+ # # return false;">Delete this post</a>
130
+ # link_to_remote "Delete this post", :update => "posts",
131
+ # :url => { :action => "destroy", :id => post.id }
132
+ #
133
+ # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
134
+ # # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
135
+ # link_to_remote(image_tag("refresh"), :update => "emails",
136
+ # :url => { :action => "list_emails" })
137
+ #
138
+ # You can override the generated HTML options by specifying a hash in
139
+ # <tt>options[:html]</tt>.
140
+ #
141
+ # link_to_remote "Delete this post", :update => "posts",
142
+ # :url => post_url(@post), :method => :delete,
143
+ # :html => { :class => "destructive" }
144
+ #
145
+ # You can also specify a hash for <tt>options[:update]</tt> to allow for
146
+ # easy redirection of output to an other DOM element if a server-side
147
+ # error occurs:
148
+ #
149
+ # Example:
150
+ # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
151
+ # # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
152
+ # link_to_remote "Delete this post",
153
+ # :url => { :action => "destroy", :id => post.id },
154
+ # :update => { :success => "posts", :failure => "error" }
155
+ #
156
+ # Optionally, you can use the <tt>options[:position]</tt> parameter to
157
+ # influence how the target DOM element is updated. It must be one of
158
+ # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
159
+ #
160
+ # The method used is by default POST. You can also specify GET or you
161
+ # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
162
+ #
163
+ # Example:
164
+ # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
165
+ # # return false;">Destroy</a>
166
+ # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
167
+ #
168
+ # By default, these remote requests are processed asynchronous during
169
+ # which various JavaScript callbacks can be triggered (for progress
170
+ # indicators and the likes). All callbacks get access to the
171
+ # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
172
+ #
173
+ # To access the server response, use <tt>request.responseText</tt>, to
174
+ # find out the HTTP status, use <tt>request.status</tt>.
175
+ #
176
+ # Example:
177
+ # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
178
+ # # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
179
+ # word = 'hello'
180
+ # link_to_remote word,
181
+ # :url => { :action => "undo", :n => word_counter },
182
+ # :complete => "undoRequestCompleted(request)"
183
+ #
184
+ # The callbacks that may be specified are (in order):
185
+ #
186
+ # <tt>:loading</tt>:: Called when the remote document is being
187
+ # loaded with data by the browser.
188
+ # <tt>:loaded</tt>:: Called when the browser has finished loading
189
+ # the remote document.
190
+ # <tt>:interactive</tt>:: Called when the user can interact with the
191
+ # remote document, even though it has not
192
+ # finished loading.
193
+ # <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
194
+ # and the HTTP status code is in the 2XX range.
195
+ # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
196
+ # and the HTTP status code is not in the 2XX
197
+ # range.
198
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
199
+ # (fires after success/failure if they are
200
+ # present).
201
+ #
202
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
203
+ # adding additional callbacks for specific status codes.
204
+ #
205
+ # Example:
206
+ # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
207
+ # # on404:function(request){alert('Not found...? Wrong URL...?')},
208
+ # # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
209
+ # link_to_remote word,
210
+ # :url => { :action => "action" },
211
+ # 404 => "alert('Not found...? Wrong URL...?')",
212
+ # :failure => "alert('HTTP Error ' + request.status + '!')"
213
+ #
214
+ # A status code callback overrides the success/failure handlers if
215
+ # present.
216
+ #
217
+ # If you for some reason or another need synchronous processing (that'll
218
+ # block the browser while the request is happening), you can specify
219
+ # <tt>options[:type] = :synchronous</tt>.
220
+ #
221
+ # You can customize further browser side call logic by passing in
222
+ # JavaScript code snippets via some optional parameters. In their order
223
+ # of use these are:
224
+ #
225
+ # <tt>:confirm</tt>:: Adds confirmation dialog.
226
+ # <tt>:condition</tt>:: Perform remote request conditionally
227
+ # by this expression. Use this to
228
+ # describe browser-side conditions when
229
+ # request should not be initiated.
230
+ # <tt>:before</tt>:: Called before request is initiated.
231
+ # <tt>:after</tt>:: Called immediately after request was
232
+ # initiated and before <tt>:loading</tt>.
233
+ # <tt>:submit</tt>:: Specifies the DOM element ID that's used
234
+ # as the parent of the form elements. By
235
+ # default this is the current form, but
236
+ # it could just as well be the ID of a
237
+ # table row or any other DOM element.
238
+ # <tt>:with</tt>:: A JavaScript expression specifying
239
+ # the parameters for the XMLHttpRequest.
240
+ # Any expressions should return a valid
241
+ # URL query string.
242
+ #
243
+ # Example:
244
+ #
245
+ # :with => "'name=' + $('name').value"
246
+ #
247
+ # You can generate a link that uses AJAX in the general case, while
248
+ # degrading gracefully to plain link behavior in the absence of
249
+ # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
250
+ # Note the extra curly braces around the <tt>options</tt> hash separate
251
+ # it as the second parameter from <tt>html_options</tt>, the third.
252
+ #
253
+ # Example:
254
+ # link_to_remote "Delete this post",
255
+ # { :update => "posts", :url => { :action => "destroy", :id => post.id } },
256
+ # :href => url_for(:action => "destroy", :id => post.id)
257
+ def link_to_remote(name, options = {}, html_options = nil)
258
+ link_to_function(name, remote_function(options), html_options || options.delete(:html))
259
+ end
260
+
261
+ # Creates a button with an onclick event which calls a remote action
262
+ # via XMLHttpRequest
263
+ # The options for specifying the target with :url
264
+ # and defining callbacks is the same as link_to_remote.
265
+ def button_to_remote(name, options = {}, html_options = {})
266
+ button_to_function(name, remote_function(options), html_options)
267
+ end
268
+
269
+ # Periodically calls the specified url (<tt>options[:url]</tt>) every
270
+ # <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
271
+ # update a specified div (<tt>options[:update]</tt>) with the results
272
+ # of the remote call. The options for specifying the target with <tt>:url</tt>
273
+ # and defining callbacks is the same as link_to_remote.
274
+ # Examples:
275
+ # # Call get_averages and put its results in 'avg' every 10 seconds
276
+ # # Generates:
277
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
278
+ # # {asynchronous:true, evalScripts:true})}, 10)
279
+ # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
280
+ #
281
+ # # Call invoice every 10 seconds with the id of the customer
282
+ # # If it succeeds, update the invoice DIV; if it fails, update the error DIV
283
+ # # Generates:
284
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
285
+ # # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
286
+ # periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
287
+ # :update => { :success => "invoice", :failure => "error" }
288
+ #
289
+ # # Call update every 20 seconds and update the new_block DIV
290
+ # # Generates:
291
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20)
292
+ # periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
293
+ #
294
+ def periodically_call_remote(options = {})
295
+ frequency = options[:frequency] || 10 # every ten seconds by default
296
+ code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
297
+ javascript_tag(code)
298
+ end
299
+
300
+ # Returns a form tag that will submit using XMLHttpRequest in the
301
+ # background instead of the regular reloading POST arrangement. Even
302
+ # though it's using JavaScript to serialize the form elements, the form
303
+ # submission will work just like a regular submission as viewed by the
304
+ # receiving side (all elements available in <tt>params</tt>). The options for
305
+ # specifying the target with <tt>:url</tt> and defining callbacks is the same as
306
+ # +link_to_remote+.
307
+ #
308
+ # A "fall-through" target for browsers that doesn't do JavaScript can be
309
+ # specified with the <tt>:action</tt>/<tt>:method</tt> options on <tt>:html</tt>.
310
+ #
311
+ # Example:
312
+ # # Generates:
313
+ # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
314
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
315
+ # form_remote_tag :html => { :action =>
316
+ # url_for(:controller => "some", :action => "place") }
317
+ #
318
+ # The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
319
+ # argument in the FormTagHelper.form_tag method.
320
+ #
321
+ # By default the fall-through action is the same as the one specified in
322
+ # the <tt>:url</tt> (and the default method is <tt>:post</tt>).
323
+ #
324
+ # form_remote_tag also takes a block, like form_tag:
325
+ # # Generates:
326
+ # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
327
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
328
+ # # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
329
+ # # </form>
330
+ # <% form_remote_tag :url => '/posts' do -%>
331
+ # <div><%= submit_tag 'Save' %></div>
332
+ # <% end -%>
333
+ def form_remote_tag(options = {}, &block)
334
+ options[:form] = true
335
+
336
+ options[:html] ||= {}
337
+ options[:html][:onsubmit] =
338
+ (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
339
+ "#{remote_function(options)}; return false;"
340
+
341
+ form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
342
+ end
343
+
344
+ # Creates a form that will submit using XMLHttpRequest in the background
345
+ # instead of the regular reloading POST arrangement and a scope around a
346
+ # specific resource that is used as a base for questioning about
347
+ # values for the fields.
348
+ #
349
+ # === Resource
350
+ #
351
+ # Example:
352
+ # <% remote_form_for(@post) do |f| %>
353
+ # ...
354
+ # <% end %>
355
+ #
356
+ # This will expand to be the same as:
357
+ #
358
+ # <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
359
+ # ...
360
+ # <% end %>
361
+ #
362
+ # === Nested Resource
363
+ #
364
+ # Example:
365
+ # <% remote_form_for([@post, @comment]) do |f| %>
366
+ # ...
367
+ # <% end %>
368
+ #
369
+ # This will expand to be the same as:
370
+ #
371
+ # <% remote_form_for :comment, @comment, :url => post_comment_path(@post, @comment), :html => { :method => :put, :class => "edit_comment", :id => "edit_comment_45" } do |f| %>
372
+ # ...
373
+ # <% end %>
374
+ #
375
+ # If you don't need to attach a form to a resource, then check out form_remote_tag.
376
+ #
377
+ # See FormHelper#form_for for additional semantics.
378
+ def remote_form_for(record_or_name_or_array, *args, &proc)
379
+ options = args.extract_options!
380
+
381
+ case record_or_name_or_array
382
+ when String, Symbol
383
+ object_name = record_or_name_or_array
384
+ when Array
385
+ object = record_or_name_or_array.last
386
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
387
+ apply_form_for_options!(record_or_name_or_array, options)
388
+ args.unshift object
389
+ else
390
+ object = record_or_name_or_array
391
+ object_name = ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array)
392
+ apply_form_for_options!(object, options)
393
+ args.unshift object
394
+ end
395
+
396
+ concat(form_remote_tag(options))
397
+ fields_for(object_name, *(args << options), &proc)
398
+ concat('</form>'.html_safe!)
399
+ end
400
+ alias_method :form_remote_for, :remote_form_for
401
+
402
+ # Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
403
+ # that will submit form using XMLHttpRequest in the background instead of a regular POST request that
404
+ # reloads the page.
405
+ #
406
+ # # Create a button that submits to the create action
407
+ # #
408
+ # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
409
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
410
+ # # return false;" type="button" value="Create" />
411
+ # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
412
+ #
413
+ # # Submit to the remote action update and update the DIV succeed or fail based
414
+ # # on the success or failure of the request
415
+ # #
416
+ # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
417
+ # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
418
+ # # return false;" type="button" value="Update" />
419
+ # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
420
+ # :update => { :success => "succeed", :failure => "fail" }
421
+ #
422
+ # <tt>options</tt> argument is the same as in form_remote_tag.
423
+ def submit_to_remote(name, value, options = {})
424
+ options[:with] ||= 'Form.serialize(this.form)'
425
+
426
+ html_options = options.delete(:html) || {}
427
+ html_options[:name] = name
428
+
429
+ button_to_remote(value, options, html_options)
430
+ end
431
+
432
+ # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
433
+ # that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple
434
+ # update return document using +update_element_function+ calls.
435
+ def evaluate_remote_response
436
+ "eval(request.responseText)"
437
+ end
438
+
439
+ # Returns the JavaScript needed for a remote function.
440
+ # Takes the same arguments as link_to_remote.
441
+ #
442
+ # Example:
443
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
444
+ # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
445
+ # <select id="options" onchange="<%= remote_function(:update => "options",
446
+ # :url => { :action => :update_options }) %>">
447
+ # <option value="0">Hello</option>
448
+ # <option value="1">World</option>
449
+ # </select>
450
+ def remote_function(options)
451
+ javascript_options = options_for_ajax(options)
452
+
453
+ update = ''
454
+ if options[:update] && options[:update].is_a?(Hash)
455
+ update = []
456
+ update << "success:'#{options[:update][:success]}'" if options[:update][:success]
457
+ update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
458
+ update = '{' + update.join(',') + '}'
459
+ elsif options[:update]
460
+ update << "'#{options[:update]}'"
461
+ end
462
+
463
+ function = update.empty? ?
464
+ "new Ajax.Request(" :
465
+ "new Ajax.Updater(#{update}, "
466
+
467
+ url_options = options[:url]
468
+ url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
469
+ function << "'#{escape_javascript(url_for(url_options))}'"
470
+ function << ", #{javascript_options})"
471
+
472
+ function = "#{options[:before]}; #{function}" if options[:before]
473
+ function = "#{function}; #{options[:after]}" if options[:after]
474
+ function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
475
+ function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
476
+
477
+ return function
478
+ end
479
+
480
+ # Observes the field with the DOM ID specified by +field_id+ and calls a
481
+ # callback when its contents have changed. The default callback is an
482
+ # Ajax call. By default the value of the observed field is sent as a
483
+ # parameter with the Ajax call.
484
+ #
485
+ # Example:
486
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
487
+ # # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
488
+ # <%= observe_field :suggest, :url => { :action => :find_suggestion },
489
+ # :frequency => 0.25,
490
+ # :update => :suggest,
491
+ # :with => 'q'
492
+ # %>
493
+ #
494
+ # Required +options+ are either of:
495
+ # <tt>:url</tt>:: +url_for+-style options for the action to call
496
+ # when the field has changed.
497
+ # <tt>:function</tt>:: Instead of making a remote call to a URL, you
498
+ # can specify javascript code to be called instead.
499
+ # Note that the value of this option is used as the
500
+ # *body* of the javascript function, a function definition
501
+ # with parameters named element and value will be generated for you
502
+ # for example:
503
+ # observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
504
+ # will generate:
505
+ # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
506
+ # The element parameter is the DOM element being observed, and the value is its value at the
507
+ # time the observer is triggered.
508
+ #
509
+ # Additional options are:
510
+ # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
511
+ # this field will be detected. Not setting this
512
+ # option at all or to a value equal to or less than
513
+ # zero will use event based observation instead of
514
+ # time based observation.
515
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
516
+ # innerHTML should be updated with the
517
+ # XMLHttpRequest response text.
518
+ # <tt>:with</tt>:: A JavaScript expression specifying the parameters
519
+ # for the XMLHttpRequest. The default is to send the
520
+ # key and value of the observed field. Any custom
521
+ # expressions should return a valid URL query string.
522
+ # The value of the field is stored in the JavaScript
523
+ # variable +value+.
524
+ #
525
+ # Examples
526
+ #
527
+ # :with => "'my_custom_key=' + value"
528
+ # :with => "'person[name]=' + prompt('New name')"
529
+ # :with => "Form.Element.serialize('other-field')"
530
+ #
531
+ # Finally
532
+ # :with => 'name'
533
+ # is shorthand for
534
+ # :with => "'name=' + value"
535
+ # This essentially just changes the key of the parameter.
536
+ #
537
+ # Additionally, you may specify any of the options documented in the
538
+ # <em>Common options</em> section at the top of this document.
539
+ #
540
+ # Example:
541
+ #
542
+ # # Sends params: {:title => 'Title of the book'} when the book_title input
543
+ # # field is changed.
544
+ # observe_field 'book_title',
545
+ # :url => 'http://example.com/books/edit/1',
546
+ # :with => 'title'
547
+ #
548
+ #
549
+ def observe_field(field_id, options = {})
550
+ if options[:frequency] && options[:frequency] > 0
551
+ build_observer('Form.Element.Observer', field_id, options)
552
+ else
553
+ build_observer('Form.Element.EventObserver', field_id, options)
554
+ end
555
+ end
556
+
557
+ # Observes the form with the DOM ID specified by +form_id+ and calls a
558
+ # callback when its contents have changed. The default callback is an
559
+ # Ajax call. By default all fields of the observed field are sent as
560
+ # parameters with the Ajax call.
561
+ #
562
+ # The +options+ for +observe_form+ are the same as the options for
563
+ # +observe_field+. The JavaScript variable +value+ available to the
564
+ # <tt>:with</tt> option is set to the serialized form by default.
565
+ def observe_form(form_id, options = {})
566
+ if options[:frequency]
567
+ build_observer('Form.Observer', form_id, options)
568
+ else
569
+ build_observer('Form.EventObserver', form_id, options)
570
+ end
571
+ end
572
+
573
+ # All the methods were moved to GeneratorMethods so that
574
+ # #include_helpers_from_context has nothing to overwrite.
575
+ class JavaScriptGenerator #:nodoc:
576
+ def initialize(context, &block) #:nodoc:
577
+ context._evaluate_assigns_and_ivars
578
+ @context, @lines = context, []
579
+ include_helpers_from_context
580
+ @context.with_output_buffer(@lines) do
581
+ @context.instance_exec(self, &block)
582
+ end
583
+ end
584
+
585
+ private
586
+ def include_helpers_from_context
587
+ extend @context.helpers if @context.respond_to?(:helpers)
588
+ extend GeneratorMethods
589
+ end
590
+
591
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
592
+ # to change the content and presentation of multiple DOM elements. Use
593
+ # this in your Ajax response bodies, either in a <script> tag or as plain
594
+ # JavaScript sent with a Content-type of "text/javascript".
595
+ #
596
+ # Create new instances with PrototypeHelper#update_page or with
597
+ # ActionController::Base#render, then call +insert_html+, +replace_html+,
598
+ # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
599
+ # methods on the yielded generator in any order you like to modify the
600
+ # content and appearance of the current page.
601
+ #
602
+ # Example:
603
+ #
604
+ # # Generates:
605
+ # # new Element.insert("list", { bottom: "<li>Some item</li>" });
606
+ # # new Effect.Highlight("list");
607
+ # # ["status-indicator", "cancel-link"].each(Element.hide);
608
+ # update_page do |page|
609
+ # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
610
+ # page.visual_effect :highlight, 'list'
611
+ # page.hide 'status-indicator', 'cancel-link'
612
+ # end
613
+ #
614
+ #
615
+ # Helper methods can be used in conjunction with JavaScriptGenerator.
616
+ # When a helper method is called inside an update block on the +page+
617
+ # object, that method will also have access to a +page+ object.
618
+ #
619
+ # Example:
620
+ #
621
+ # module ApplicationHelper
622
+ # def update_time
623
+ # page.replace_html 'time', Time.now.to_s(:db)
624
+ # page.visual_effect :highlight, 'time'
625
+ # end
626
+ # end
627
+ #
628
+ # # Controller action
629
+ # def poll
630
+ # render(:update) { |page| page.update_time }
631
+ # end
632
+ #
633
+ # Calls to JavaScriptGenerator not matching a helper method below
634
+ # generate a proxy to the JavaScript Class named by the method called.
635
+ #
636
+ # Examples:
637
+ #
638
+ # # Generates:
639
+ # # Foo.init();
640
+ # update_page do |page|
641
+ # page.foo.init
642
+ # end
643
+ #
644
+ # # Generates:
645
+ # # Event.observe('one', 'click', function () {
646
+ # # $('two').show();
647
+ # # });
648
+ # update_page do |page|
649
+ # page.event.observe('one', 'click') do |p|
650
+ # p[:two].show
651
+ # end
652
+ # end
653
+ #
654
+ # You can also use PrototypeHelper#update_page_tag instead of
655
+ # PrototypeHelper#update_page to wrap the generated JavaScript in a
656
+ # <script> tag.
657
+ module GeneratorMethods
658
+ def to_s #:nodoc:
659
+ returning javascript = @lines * $/ do
660
+ if ActionView::Base.debug_rjs
661
+ source = javascript.dup
662
+ javascript.replace "try {\n#{source}\n} catch (e) "
663
+ javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
664
+ end
665
+ end
666
+ end
667
+
668
+ # Returns a element reference by finding it through +id+ in the DOM. This element can then be
669
+ # used for further method calls. Examples:
670
+ #
671
+ # page['blank_slate'] # => $('blank_slate');
672
+ # page['blank_slate'].show # => $('blank_slate').show();
673
+ # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
674
+ #
675
+ # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
676
+ # the correct id:
677
+ #
678
+ # page[@post] # => $('post_45')
679
+ # page[Post.new] # => $('new_post')
680
+ def [](id)
681
+ case id
682
+ when String, Symbol, NilClass
683
+ JavaScriptElementProxy.new(self, id)
684
+ else
685
+ JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
686
+ end
687
+ end
688
+
689
+ # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
690
+ # expression as an argument to another JavaScriptGenerator method.
691
+ def literal(code)
692
+ ::ActiveSupport::JSON::Variable.new(code.to_s)
693
+ end
694
+
695
+ # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
696
+ # used for further method calls. Examples:
697
+ #
698
+ # page.select('p') # => $$('p');
699
+ # page.select('p.welcome b').first # => $$('p.welcome b').first();
700
+ # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
701
+ #
702
+ # You can also use prototype enumerations with the collection. Observe:
703
+ #
704
+ # # Generates: $$('#items li').each(function(value) { value.hide(); });
705
+ # page.select('#items li').each do |value|
706
+ # value.hide
707
+ # end
708
+ #
709
+ # Though you can call the block param anything you want, they are always rendered in the
710
+ # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
711
+ #
712
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
713
+ # page.select('#items li').collect('hidden') do |item|
714
+ # item.hide
715
+ # end
716
+ #
717
+ def select(pattern)
718
+ JavaScriptElementCollectionProxy.new(self, pattern)
719
+ end
720
+
721
+ # Inserts HTML at the specified +position+ relative to the DOM element
722
+ # identified by the given +id+.
723
+ #
724
+ # +position+ may be one of:
725
+ #
726
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
727
+ # element's existing content.
728
+ # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
729
+ # element's existing content.
730
+ # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
731
+ # <tt>:after</tt>:: HTML is inserted immediately following the element.
732
+ #
733
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
734
+ # of options to be passed to ActionView::Base#render. For example:
735
+ #
736
+ # # Insert the rendered 'navigation' partial just before the DOM
737
+ # # element with ID 'content'.
738
+ # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
739
+ # page.insert_html :before, 'content', :partial => 'navigation'
740
+ #
741
+ # # Add a list item to the bottom of the <ul> with ID 'list'.
742
+ # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
743
+ # page.insert_html :bottom, 'list', '<li>Last item</li>'
744
+ #
745
+ def insert_html(position, id, *options_for_render)
746
+ content = javascript_object_for(render(*options_for_render))
747
+ record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
748
+ end
749
+
750
+ # Replaces the inner HTML of the DOM element with the given +id+.
751
+ #
752
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
753
+ # of options to be passed to ActionView::Base#render. For example:
754
+ #
755
+ # # Replace the HTML of the DOM element having ID 'person-45' with the
756
+ # # 'person' partial for the appropriate object.
757
+ # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
758
+ # page.replace_html 'person-45', :partial => 'person', :object => @person
759
+ #
760
+ def replace_html(id, *options_for_render)
761
+ call 'Element.update', id, render(*options_for_render)
762
+ end
763
+
764
+ # Replaces the "outer HTML" (i.e., the entire element, not just its
765
+ # contents) of the DOM element with the given +id+.
766
+ #
767
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
768
+ # of options to be passed to ActionView::Base#render. For example:
769
+ #
770
+ # # Replace the DOM element having ID 'person-45' with the
771
+ # # 'person' partial for the appropriate object.
772
+ # page.replace 'person-45', :partial => 'person', :object => @person
773
+ #
774
+ # This allows the same partial that is used for the +insert_html+ to
775
+ # be also used for the input to +replace+ without resorting to
776
+ # the use of wrapper elements.
777
+ #
778
+ # Examples:
779
+ #
780
+ # <div id="people">
781
+ # <%= render :partial => 'person', :collection => @people %>
782
+ # </div>
783
+ #
784
+ # # Insert a new person
785
+ # #
786
+ # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
787
+ # page.insert_html :bottom, :partial => 'person', :object => @person
788
+ #
789
+ # # Replace an existing person
790
+ #
791
+ # # Generates: Element.replace("person_45", "-- Contents of partial --");
792
+ # page.replace 'person_45', :partial => 'person', :object => @person
793
+ #
794
+ def replace(id, *options_for_render)
795
+ call 'Element.replace', id, render(*options_for_render)
796
+ end
797
+
798
+ # Removes the DOM elements with the given +ids+ from the page.
799
+ #
800
+ # Example:
801
+ #
802
+ # # Remove a few people
803
+ # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
804
+ # page.remove 'person_23', 'person_9', 'person_2'
805
+ #
806
+ def remove(*ids)
807
+ loop_on_multiple_args 'Element.remove', ids
808
+ end
809
+
810
+ # Shows hidden DOM elements with the given +ids+.
811
+ #
812
+ # Example:
813
+ #
814
+ # # Show a few people
815
+ # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
816
+ # page.show 'person_6', 'person_13', 'person_223'
817
+ #
818
+ def show(*ids)
819
+ loop_on_multiple_args 'Element.show', ids
820
+ end
821
+
822
+ # Hides the visible DOM elements with the given +ids+.
823
+ #
824
+ # Example:
825
+ #
826
+ # # Hide a few people
827
+ # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
828
+ # page.hide 'person_29', 'person_9', 'person_0'
829
+ #
830
+ def hide(*ids)
831
+ loop_on_multiple_args 'Element.hide', ids
832
+ end
833
+
834
+ # Toggles the visibility of the DOM elements with the given +ids+.
835
+ # Example:
836
+ #
837
+ # # Show a few people
838
+ # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
839
+ # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
840
+ # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
841
+ #
842
+ def toggle(*ids)
843
+ loop_on_multiple_args 'Element.toggle', ids
844
+ end
845
+
846
+ # Displays an alert dialog with the given +message+.
847
+ #
848
+ # Example:
849
+ #
850
+ # # Generates: alert('This message is from Rails!')
851
+ # page.alert('This message is from Rails!')
852
+ def alert(message)
853
+ call 'alert', message
854
+ end
855
+
856
+ # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
857
+ #
858
+ # Examples:
859
+ #
860
+ # # Generates: window.location.href = "/mycontroller";
861
+ # page.redirect_to(:action => 'index')
862
+ #
863
+ # # Generates: window.location.href = "/account/signup";
864
+ # page.redirect_to(:controller => 'account', :action => 'signup')
865
+ def redirect_to(location)
866
+ url = location.is_a?(String) ? location : @context.url_for(location)
867
+ record "window.location.href = #{url.inspect}"
868
+ end
869
+
870
+ # Reloads the browser's current +location+ using JavaScript
871
+ #
872
+ # Examples:
873
+ #
874
+ # # Generates: window.location.reload();
875
+ # page.reload
876
+ def reload
877
+ record 'window.location.reload()'
878
+ end
879
+
880
+ # Calls the JavaScript +function+, optionally with the given +arguments+.
881
+ #
882
+ # If a block is given, the block will be passed to a new JavaScriptGenerator;
883
+ # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
884
+ # and passed as the called function's final argument.
885
+ #
886
+ # Examples:
887
+ #
888
+ # # Generates: Element.replace(my_element, "My content to replace with.")
889
+ # page.call 'Element.replace', 'my_element', "My content to replace with."
890
+ #
891
+ # # Generates: alert('My message!')
892
+ # page.call 'alert', 'My message!'
893
+ #
894
+ # # Generates:
895
+ # # my_method(function() {
896
+ # # $("one").show();
897
+ # # $("two").hide();
898
+ # # });
899
+ # page.call(:my_method) do |p|
900
+ # p[:one].show
901
+ # p[:two].hide
902
+ # end
903
+ def call(function, *arguments, &block)
904
+ record "#{function}(#{arguments_for_call(arguments, block)})"
905
+ end
906
+
907
+ # Assigns the JavaScript +variable+ the given +value+.
908
+ #
909
+ # Examples:
910
+ #
911
+ # # Generates: my_string = "This is mine!";
912
+ # page.assign 'my_string', 'This is mine!'
913
+ #
914
+ # # Generates: record_count = 33;
915
+ # page.assign 'record_count', 33
916
+ #
917
+ # # Generates: tabulated_total = 47
918
+ # page.assign 'tabulated_total', @total_from_cart
919
+ #
920
+ def assign(variable, value)
921
+ record "#{variable} = #{javascript_object_for(value)}"
922
+ end
923
+
924
+ # Writes raw JavaScript to the page.
925
+ #
926
+ # Example:
927
+ #
928
+ # page << "alert('JavaScript with Prototype.');"
929
+ def <<(javascript)
930
+ @lines << javascript
931
+ end
932
+
933
+ # Executes the content of the block after a delay of +seconds+. Example:
934
+ #
935
+ # # Generates:
936
+ # # setTimeout(function() {
937
+ # # ;
938
+ # # new Effect.Fade("notice",{});
939
+ # # }, 20000);
940
+ # page.delay(20) do
941
+ # page.visual_effect :fade, 'notice'
942
+ # end
943
+ def delay(seconds = 1)
944
+ record "setTimeout(function() {\n\n"
945
+ yield
946
+ record "}, #{(seconds * 1000).to_i})"
947
+ end
948
+
949
+ # Starts a script.aculo.us visual effect. See
950
+ # ActionView::Helpers::ScriptaculousHelper for more information.
951
+ def visual_effect(name, id = nil, options = {})
952
+ record @context.send(:visual_effect, name, id, options)
953
+ end
954
+
955
+ # Creates a script.aculo.us sortable element. Useful
956
+ # to recreate sortable elements after items get added
957
+ # or deleted.
958
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
959
+ def sortable(id, options = {})
960
+ record @context.send(:sortable_element_js, id, options)
961
+ end
962
+
963
+ # Creates a script.aculo.us draggable element.
964
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
965
+ def draggable(id, options = {})
966
+ record @context.send(:draggable_element_js, id, options)
967
+ end
968
+
969
+ # Creates a script.aculo.us drop receiving element.
970
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
971
+ def drop_receiving(id, options = {})
972
+ record @context.send(:drop_receiving_element_js, id, options)
973
+ end
974
+
975
+ private
976
+ def loop_on_multiple_args(method, ids)
977
+ record(ids.size>1 ?
978
+ "#{javascript_object_for(ids)}.each(#{method})" :
979
+ "#{method}(#{javascript_object_for(ids.first)})")
980
+ end
981
+
982
+ def page
983
+ self
984
+ end
985
+
986
+ def record(line)
987
+ returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
988
+ self << line
989
+ end
990
+ end
991
+
992
+ def render(*options_for_render)
993
+ old_formats = @context && @context.formats
994
+
995
+ @context.reset_formats([:html]) if @context
996
+ Hash === options_for_render.first ?
997
+ @context.render(*options_for_render) :
998
+ options_for_render.first.to_s
999
+ ensure
1000
+ @context.reset_formats(old_formats) if @context
1001
+ end
1002
+
1003
+ def javascript_object_for(object)
1004
+ ::ActiveSupport::JSON.encode(object)
1005
+ end
1006
+
1007
+ def arguments_for_call(arguments, block = nil)
1008
+ arguments << block_to_function(block) if block
1009
+ arguments.map { |argument| javascript_object_for(argument) }.join ', '
1010
+ end
1011
+
1012
+ def block_to_function(block)
1013
+ generator = self.class.new(@context, &block)
1014
+ literal("function() { #{generator.to_s} }")
1015
+ end
1016
+
1017
+ def method_missing(method, *arguments)
1018
+ JavaScriptProxy.new(self, method.to_s.camelize)
1019
+ end
1020
+ end
1021
+ end
1022
+
1023
+ # Yields a JavaScriptGenerator and returns the generated JavaScript code.
1024
+ # Use this to update multiple elements on a page in an Ajax response.
1025
+ # See JavaScriptGenerator for more information.
1026
+ #
1027
+ # Example:
1028
+ #
1029
+ # update_page do |page|
1030
+ # page.hide 'spinner'
1031
+ # end
1032
+ def update_page(&block)
1033
+ JavaScriptGenerator.new(@template, &block).to_s
1034
+ end
1035
+
1036
+ # Works like update_page but wraps the generated JavaScript in a <script>
1037
+ # tag. Use this to include generated JavaScript in an ERb template.
1038
+ # See JavaScriptGenerator for more information.
1039
+ #
1040
+ # +html_options+ may be a hash of <script> attributes to be passed
1041
+ # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
1042
+ def update_page_tag(html_options = {}, &block)
1043
+ javascript_tag update_page(&block), html_options
1044
+ end
1045
+
1046
+ protected
1047
+ def options_for_ajax(options)
1048
+ js_options = build_callbacks(options)
1049
+
1050
+ js_options['asynchronous'] = options[:type] != :synchronous
1051
+ js_options['method'] = method_option_to_s(options[:method]) if options[:method]
1052
+ js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
1053
+ js_options['evalScripts'] = options[:script].nil? || options[:script]
1054
+
1055
+ if options[:form]
1056
+ js_options['parameters'] = 'Form.serialize(this)'
1057
+ elsif options[:submit]
1058
+ js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
1059
+ elsif options[:with]
1060
+ js_options['parameters'] = options[:with]
1061
+ end
1062
+
1063
+ if protect_against_forgery? && !options[:form]
1064
+ if js_options['parameters']
1065
+ js_options['parameters'] << " + '&"
1066
+ else
1067
+ js_options['parameters'] = "'"
1068
+ end
1069
+ js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
1070
+ end
1071
+
1072
+ options_for_javascript(js_options)
1073
+ end
1074
+
1075
+ def method_option_to_s(method)
1076
+ (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
1077
+ end
1078
+
1079
+ def build_observer(klass, name, options = {})
1080
+ if options[:with] && (options[:with] !~ /[\{=(.]/)
1081
+ options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
1082
+ else
1083
+ options[:with] ||= 'value' unless options[:function]
1084
+ end
1085
+
1086
+ callback = options[:function] || remote_function(options)
1087
+ javascript = "new #{klass}('#{name}', "
1088
+ javascript << "#{options[:frequency]}, " if options[:frequency]
1089
+ javascript << "function(element, value) {"
1090
+ javascript << "#{callback}}"
1091
+ javascript << ")"
1092
+ javascript_tag(javascript)
1093
+ end
1094
+
1095
+ def build_callbacks(options)
1096
+ callbacks = {}
1097
+ options.each do |callback, code|
1098
+ if CALLBACKS.include?(callback)
1099
+ name = 'on' + callback.to_s.capitalize
1100
+ callbacks[name] = "function(request){#{code}}"
1101
+ end
1102
+ end
1103
+ callbacks
1104
+ end
1105
+ end
1106
+
1107
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
1108
+ class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
1109
+
1110
+ def initialize(generator, root = nil)
1111
+ @generator = generator
1112
+ @generator << root if root
1113
+ end
1114
+
1115
+ private
1116
+ def method_missing(method, *arguments, &block)
1117
+ if method.to_s =~ /(.*)=$/
1118
+ assign($1, arguments.first)
1119
+ else
1120
+ call("#{method.to_s.camelize(:lower)}", *arguments, &block)
1121
+ end
1122
+ end
1123
+
1124
+ def call(function, *arguments, &block)
1125
+ append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
1126
+ self
1127
+ end
1128
+
1129
+ def assign(variable, value)
1130
+ append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
1131
+ end
1132
+
1133
+ def function_chain
1134
+ @function_chain ||= @generator.instance_variable_get(:@lines)
1135
+ end
1136
+
1137
+ def append_to_function_chain!(call)
1138
+ function_chain[-1].chomp!(';')
1139
+ function_chain[-1] += ".#{call};"
1140
+ end
1141
+ end
1142
+
1143
+ class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
1144
+ def initialize(generator, id)
1145
+ @id = id
1146
+ super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
1147
+ end
1148
+
1149
+ # Allows access of element attributes through +attribute+. Examples:
1150
+ #
1151
+ # page['foo']['style'] # => $('foo').style;
1152
+ # page['foo']['style']['color'] # => $('blank_slate').style.color;
1153
+ # page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
1154
+ # page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
1155
+ def [](attribute)
1156
+ append_to_function_chain!(attribute)
1157
+ self
1158
+ end
1159
+
1160
+ def []=(variable, value)
1161
+ assign(variable, value)
1162
+ end
1163
+
1164
+ def replace_html(*options_for_render)
1165
+ call 'update', @generator.send(:render, *options_for_render)
1166
+ end
1167
+
1168
+ def replace(*options_for_render)
1169
+ call 'replace', @generator.send(:render, *options_for_render)
1170
+ end
1171
+
1172
+ def reload(options_for_replace = {})
1173
+ replace(options_for_replace.merge({ :partial => @id.to_s }))
1174
+ end
1175
+
1176
+ end
1177
+
1178
+ class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
1179
+ def initialize(generator, variable)
1180
+ @variable = ::ActiveSupport::JSON::Variable.new(variable)
1181
+ @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
1182
+ super(generator)
1183
+ end
1184
+
1185
+ # The JSON Encoder calls this to check for the +to_json+ method
1186
+ # Since it's a blank slate object, I suppose it responds to anything.
1187
+ def respond_to?(*)
1188
+ true
1189
+ end
1190
+
1191
+ def as_json(options = nil)
1192
+ @variable
1193
+ end
1194
+
1195
+ private
1196
+ def append_to_function_chain!(call)
1197
+ @generator << @variable if @empty
1198
+ @empty = false
1199
+ super
1200
+ end
1201
+ end
1202
+
1203
+ class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
1204
+ ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by, :in_groups_of, :each_slice] unless defined? ENUMERABLE_METHODS_WITH_RETURN
1205
+ ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
1206
+ attr_reader :generator
1207
+ delegate :arguments_for_call, :to => :generator
1208
+
1209
+ def initialize(generator, pattern)
1210
+ super(generator, @pattern = pattern)
1211
+ end
1212
+
1213
+ def each_slice(variable, number, &block)
1214
+ if block
1215
+ enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
1216
+ else
1217
+ add_variable_assignment!(variable)
1218
+ append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
1219
+ end
1220
+ end
1221
+
1222
+ def grep(variable, pattern, &block)
1223
+ enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
1224
+ end
1225
+
1226
+ def in_groups_of(variable, number, fill_with = nil)
1227
+ arguments = [number]
1228
+ arguments << fill_with unless fill_with.nil?
1229
+ add_variable_assignment!(variable)
1230
+ append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
1231
+ end
1232
+
1233
+ def inject(variable, memo, &block)
1234
+ enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
1235
+ end
1236
+
1237
+ def pluck(variable, property)
1238
+ add_variable_assignment!(variable)
1239
+ append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
1240
+ end
1241
+
1242
+ def zip(variable, *arguments, &block)
1243
+ add_variable_assignment!(variable)
1244
+ append_enumerable_function!("zip(#{arguments_for_call arguments}")
1245
+ if block
1246
+ function_chain[-1] += ", function(array) {"
1247
+ yield ::ActiveSupport::JSON::Variable.new('array')
1248
+ add_return_statement!
1249
+ @generator << '});'
1250
+ else
1251
+ function_chain[-1] += ');'
1252
+ end
1253
+ end
1254
+
1255
+ private
1256
+ def method_missing(method, *arguments, &block)
1257
+ if ENUMERABLE_METHODS.include?(method)
1258
+ returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
1259
+ variable = arguments.first if returnable
1260
+ enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
1261
+ else
1262
+ super
1263
+ end
1264
+ end
1265
+
1266
+ # Options
1267
+ # * variable - name of the variable to set the result of the enumeration to
1268
+ # * method_args - array of the javascript enumeration method args that occur before the function
1269
+ # * yield_args - array of the javascript yield args
1270
+ # * return - true if the enumeration should return the last statement
1271
+ def enumerate(enumerable, options = {}, &block)
1272
+ options[:method_args] ||= []
1273
+ options[:yield_args] ||= []
1274
+ yield_args = options[:yield_args] * ', '
1275
+ method_args = arguments_for_call options[:method_args] # foo, bar, function
1276
+ method_args << ', ' unless method_args.blank?
1277
+ add_variable_assignment!(options[:variable]) if options[:variable]
1278
+ append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
1279
+ # only yield as many params as were passed in the block
1280
+ yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
1281
+ add_return_statement! if options[:return]
1282
+ @generator << '});'
1283
+ end
1284
+
1285
+ def add_variable_assignment!(variable)
1286
+ function_chain.push("var #{variable} = #{function_chain.pop}")
1287
+ end
1288
+
1289
+ def add_return_statement!
1290
+ unless function_chain.last =~ /return/
1291
+ function_chain.push("return #{function_chain.pop.chomp(';')};")
1292
+ end
1293
+ end
1294
+
1295
+ def append_enumerable_function!(call)
1296
+ function_chain[-1].chomp!(';')
1297
+ function_chain[-1] += ".#{call}"
1298
+ end
1299
+ end
1300
+
1301
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
1302
+ def initialize(generator, pattern)
1303
+ super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
1304
+ end
1305
+ end
1306
+ end
1307
+ end
1308
+
1309
+ require 'action_view/helpers/javascript_helper'