actionpack 1.13.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (317) hide show
  1. data/CHANGELOG +1400 -20
  2. data/MIT-LICENSE +1 -1
  3. data/README +5 -5
  4. data/RUNNING_UNIT_TESTS +4 -5
  5. data/Rakefile +5 -6
  6. data/install.rb +2 -2
  7. data/lib/action_controller.rb +11 -15
  8. data/lib/action_controller/assertions.rb +12 -25
  9. data/lib/action_controller/assertions/dom_assertions.rb +18 -4
  10. data/lib/action_controller/assertions/model_assertions.rb +8 -1
  11. data/lib/action_controller/assertions/response_assertions.rb +35 -12
  12. data/lib/action_controller/assertions/routing_assertions.rb +56 -12
  13. data/lib/action_controller/assertions/selector_assertions.rb +105 -38
  14. data/lib/action_controller/assertions/tag_assertions.rb +28 -15
  15. data/lib/action_controller/base.rb +318 -250
  16. data/lib/action_controller/benchmarking.rb +33 -29
  17. data/lib/action_controller/caching.rb +130 -64
  18. data/lib/action_controller/cgi_ext.rb +16 -0
  19. data/lib/action_controller/cgi_ext/{cookie_performance_fix.rb → cookie.rb} +25 -40
  20. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  21. data/lib/action_controller/cgi_ext/session.rb +73 -0
  22. data/lib/action_controller/cgi_ext/stdinput.rb +23 -0
  23. data/lib/action_controller/cgi_process.rb +34 -57
  24. data/lib/action_controller/components.rb +19 -36
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/dispatcher.rb +195 -0
  27. data/lib/action_controller/filters.rb +35 -34
  28. data/lib/action_controller/flash.rb +30 -35
  29. data/lib/action_controller/helpers.rb +121 -47
  30. data/lib/action_controller/http_authentication.rb +126 -0
  31. data/lib/action_controller/integration.rb +105 -101
  32. data/lib/action_controller/layout.rb +59 -47
  33. data/lib/action_controller/mime_responds.rb +57 -68
  34. data/lib/action_controller/mime_type.rb +43 -80
  35. data/lib/action_controller/mime_types.rb +20 -0
  36. data/lib/action_controller/polymorphic_routes.rb +88 -0
  37. data/lib/action_controller/record_identifier.rb +91 -0
  38. data/lib/action_controller/request.rb +553 -88
  39. data/lib/action_controller/request_forgery_protection.rb +126 -0
  40. data/lib/action_controller/request_profiler.rb +138 -0
  41. data/lib/action_controller/rescue.rb +185 -69
  42. data/lib/action_controller/resources.rb +211 -172
  43. data/lib/action_controller/response.rb +49 -8
  44. data/lib/action_controller/routing.rb +359 -236
  45. data/lib/action_controller/routing_optimisation.rb +119 -0
  46. data/lib/action_controller/session/active_record_store.rb +3 -2
  47. data/lib/action_controller/session/cookie_store.rb +161 -0
  48. data/lib/action_controller/session/mem_cache_store.rb +9 -16
  49. data/lib/action_controller/session_management.rb +17 -8
  50. data/lib/action_controller/streaming.rb +6 -3
  51. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  52. data/lib/action_controller/templates/rescues/{_trace.rhtml → _trace.erb} +0 -0
  53. data/lib/action_controller/templates/rescues/{diagnostics.rhtml → diagnostics.erb} +2 -2
  54. data/lib/action_controller/templates/rescues/{layout.rhtml → layout.erb} +0 -0
  55. data/lib/action_controller/templates/rescues/{missing_template.rhtml → missing_template.erb} +0 -0
  56. data/lib/action_controller/templates/rescues/{routing_error.rhtml → routing_error.erb} +0 -0
  57. data/lib/action_controller/templates/rescues/{template_error.rhtml → template_error.erb} +2 -2
  58. data/lib/action_controller/templates/rescues/{unknown_action.rhtml → unknown_action.erb} +0 -0
  59. data/lib/action_controller/test_case.rb +53 -0
  60. data/lib/action_controller/test_process.rb +59 -46
  61. data/lib/action_controller/url_rewriter.rb +48 -24
  62. data/lib/action_controller/vendor/html-scanner/html/document.rb +7 -4
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +11 -6
  65. data/lib/action_controller/verification.rb +27 -21
  66. data/lib/action_pack.rb +1 -1
  67. data/lib/action_pack/version.rb +4 -4
  68. data/lib/action_view.rb +2 -3
  69. data/lib/action_view/base.rb +218 -63
  70. data/lib/action_view/compiled_templates.rb +1 -2
  71. data/lib/action_view/helpers/active_record_helper.rb +35 -17
  72. data/lib/action_view/helpers/asset_tag_helper.rb +395 -87
  73. data/lib/action_view/helpers/atom_feed_helper.rb +111 -0
  74. data/lib/action_view/helpers/benchmark_helper.rb +12 -5
  75. data/lib/action_view/helpers/cache_helper.rb +29 -0
  76. data/lib/action_view/helpers/capture_helper.rb +97 -63
  77. data/lib/action_view/helpers/date_helper.rb +295 -35
  78. data/lib/action_view/helpers/debug_helper.rb +6 -2
  79. data/lib/action_view/helpers/form_helper.rb +354 -111
  80. data/lib/action_view/helpers/form_options_helper.rb +171 -109
  81. data/lib/action_view/helpers/form_tag_helper.rb +332 -76
  82. data/lib/action_view/helpers/javascript_helper.rb +35 -11
  83. data/lib/action_view/helpers/javascripts/controls.js +484 -354
  84. data/lib/action_view/helpers/javascripts/dragdrop.js +88 -58
  85. data/lib/action_view/helpers/javascripts/effects.js +396 -364
  86. data/lib/action_view/helpers/javascripts/prototype.js +2817 -1107
  87. data/lib/action_view/helpers/number_helper.rb +84 -60
  88. data/lib/action_view/helpers/prototype_helper.rb +419 -43
  89. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  90. data/lib/action_view/helpers/record_tag_helper.rb +59 -0
  91. data/lib/action_view/helpers/sanitize_helper.rb +223 -0
  92. data/lib/action_view/helpers/scriptaculous_helper.rb +63 -4
  93. data/lib/action_view/helpers/tag_helper.rb +69 -39
  94. data/lib/action_view/helpers/text_helper.rb +221 -148
  95. data/lib/action_view/helpers/url_helper.rb +283 -165
  96. data/lib/action_view/partials.rb +134 -62
  97. data/lib/action_view/template_error.rb +4 -12
  98. data/lib/actionpack.rb +1 -0
  99. data/test/abstract_unit.rb +21 -1
  100. data/test/action_view_test.rb +26 -0
  101. data/test/active_record_unit.rb +12 -20
  102. data/test/activerecord/active_record_store_test.rb +2 -2
  103. data/test/activerecord/render_partial_with_record_identification_test.rb +74 -0
  104. data/test/controller/action_pack_assertions_test.rb +21 -152
  105. data/test/controller/addresses_render_test.rb +2 -7
  106. data/test/controller/assert_select_test.rb +120 -14
  107. data/test/controller/base_test.rb +11 -13
  108. data/test/controller/caching_test.rb +125 -5
  109. data/test/controller/capture_test.rb +23 -16
  110. data/test/controller/cgi_test.rb +66 -391
  111. data/test/controller/components_test.rb +31 -42
  112. data/test/controller/content_type_test.rb +1 -1
  113. data/test/controller/cookie_test.rb +42 -14
  114. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -42
  115. data/test/controller/dispatcher_test.rb +123 -0
  116. data/test/controller/fake_models.rb +5 -0
  117. data/test/controller/filters_test.rb +44 -7
  118. data/test/controller/flash_test.rb +46 -2
  119. data/test/controller/fragment_store_setting_test.rb +10 -8
  120. data/test/controller/helper_test.rb +19 -2
  121. data/test/controller/html-scanner/document_test.rb +124 -0
  122. data/test/controller/html-scanner/node_test.rb +69 -0
  123. data/test/controller/html-scanner/sanitizer_test.rb +250 -0
  124. data/test/controller/html-scanner/tag_node_test.rb +239 -0
  125. data/test/controller/html-scanner/text_node_test.rb +51 -0
  126. data/test/controller/html-scanner/tokenizer_test.rb +125 -0
  127. data/test/controller/http_authentication_test.rb +54 -0
  128. data/test/controller/integration_test.rb +12 -26
  129. data/test/controller/layout_test.rb +64 -12
  130. data/test/controller/mime_responds_test.rb +193 -38
  131. data/test/controller/mime_type_test.rb +30 -8
  132. data/test/controller/new_render_test.rb +104 -22
  133. data/test/controller/polymorphic_routes_test.rb +98 -0
  134. data/test/controller/record_identifier_test.rb +103 -0
  135. data/test/controller/redirect_test.rb +120 -18
  136. data/test/controller/render_test.rb +195 -45
  137. data/test/controller/request_forgery_protection_test.rb +217 -0
  138. data/test/controller/request_test.rb +545 -27
  139. data/test/controller/rescue_test.rb +501 -0
  140. data/test/controller/resources_test.rb +258 -132
  141. data/test/controller/routing_test.rb +502 -106
  142. data/test/controller/selector_test.rb +5 -5
  143. data/test/controller/send_file_test.rb +17 -7
  144. data/test/controller/session/cookie_store_test.rb +246 -0
  145. data/test/controller/session/mem_cache_store_test.rb +182 -0
  146. data/test/controller/session_fixation_test.rb +8 -11
  147. data/test/controller/session_management_test.rb +7 -7
  148. data/test/controller/test_test.rb +150 -38
  149. data/test/controller/url_rewriter_test.rb +87 -12
  150. data/test/controller/verification_test.rb +11 -0
  151. data/test/controller/view_paths_test.rb +137 -0
  152. data/test/controller/webservice_test.rb +11 -75
  153. data/test/fixtures/addresses/{list.rhtml → list.erb} +0 -0
  154. data/test/fixtures/db_definitions/sqlite.sql +2 -1
  155. data/test/fixtures/developer.rb +2 -0
  156. data/test/fixtures/fun/games/{hello_world.rhtml → hello_world.erb} +0 -0
  157. data/test/fixtures/helpers/fun/pdf_helper.rb +1 -1
  158. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  159. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  160. data/test/fixtures/layouts/{builder.rxml → builder.builder} +0 -0
  161. data/test/fixtures/layouts/{standard.rhtml → standard.erb} +0 -0
  162. data/test/fixtures/layouts/{talk_from_action.rhtml → talk_from_action.erb} +0 -0
  163. data/test/fixtures/layouts/{yield.rhtml → yield.erb} +0 -0
  164. data/test/fixtures/multipart/binary_file +0 -0
  165. data/test/fixtures/multipart/bracketed_param +5 -0
  166. data/test/fixtures/override/test/hello_world.erb +1 -0
  167. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  168. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  169. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  170. data/test/fixtures/post_test/post/index.html.erb +1 -0
  171. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  172. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  173. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  174. data/test/fixtures/public/404.html +1 -0
  175. data/test/fixtures/public/500.html +1 -0
  176. data/test/fixtures/public/javascripts/application.js +0 -1
  177. data/test/fixtures/public/javascripts/bank.js +1 -0
  178. data/test/fixtures/public/javascripts/robber.js +1 -0
  179. data/test/fixtures/public/stylesheets/bank.css +1 -0
  180. data/test/fixtures/public/stylesheets/robber.css +1 -0
  181. data/test/fixtures/replies.yml +2 -0
  182. data/test/fixtures/reply.rb +2 -1
  183. data/test/fixtures/respond_to/{all_types_with_layout.rhtml → all_types_with_layout.html.erb} +0 -0
  184. data/test/fixtures/respond_to/{all_types_with_layout.rjs → all_types_with_layout.js.rjs} +0 -0
  185. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  186. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  187. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  188. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  189. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  190. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  191. data/test/fixtures/respond_to/{using_defaults.rhtml → using_defaults.html.erb} +0 -0
  192. data/test/fixtures/respond_to/{using_defaults.rjs → using_defaults.js.rjs} +0 -0
  193. data/test/fixtures/respond_to/{using_defaults.rxml → using_defaults.xml.builder} +0 -0
  194. data/test/fixtures/respond_to/{using_defaults_with_type_list.rhtml → using_defaults_with_type_list.html.erb} +0 -0
  195. data/test/fixtures/respond_to/{using_defaults_with_type_list.rjs → using_defaults_with_type_list.js.rjs} +0 -0
  196. data/test/fixtures/respond_to/{using_defaults_with_type_list.rxml → using_defaults_with_type_list.xml.builder} +0 -0
  197. data/test/fixtures/scope/test/{modgreet.rhtml → modgreet.erb} +0 -0
  198. data/test/fixtures/test/{_customer.rhtml → _customer.erb} +0 -0
  199. data/test/fixtures/test/{_customer_greeting.rhtml → _customer_greeting.erb} +0 -0
  200. data/test/fixtures/test/_hash_greeting.erb +1 -0
  201. data/test/fixtures/test/_hash_object.erb +2 -0
  202. data/test/fixtures/test/{_hello.rxml → _hello.builder} +0 -0
  203. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  204. data/test/fixtures/test/_partial.erb +1 -0
  205. data/test/fixtures/test/_partial.html.erb +1 -0
  206. data/test/fixtures/test/_partial.js.erb +1 -0
  207. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  208. data/test/fixtures/test/{_partial_only.rhtml → _partial_only.erb} +0 -0
  209. data/test/fixtures/test/{_person.rhtml → _person.erb} +0 -0
  210. data/test/fixtures/test/{action_talk_to_layout.rhtml → action_talk_to_layout.erb} +0 -0
  211. data/test/fixtures/test/{block_content_for.rhtml → block_content_for.erb} +0 -0
  212. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  213. data/test/fixtures/test/{capturing.rhtml → capturing.erb} +0 -0
  214. data/test/fixtures/test/{content_for.rhtml → content_for.erb} +0 -0
  215. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  216. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  217. data/test/fixtures/test/dot.directory/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  218. data/test/fixtures/test/{erb_content_for.rhtml → erb_content_for.erb} +0 -0
  219. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  220. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  221. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  222. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  223. data/test/fixtures/test/{greeting.rhtml → greeting.erb} +0 -0
  224. data/test/fixtures/test/{hello.rxml → hello.builder} +0 -0
  225. data/test/fixtures/test/{hello_world.rxml → hello_world.builder} +0 -0
  226. data/test/fixtures/test/{hello_world.rhtml → hello_world.erb} +0 -0
  227. data/test/fixtures/test/{hello_world_container.rxml → hello_world_container.builder} +0 -0
  228. data/test/fixtures/test/{hello_world_with_layout_false.rhtml → hello_world_with_layout_false.erb} +0 -0
  229. data/test/fixtures/test/{hello_xml_world.rxml → hello_xml_world.builder} +0 -0
  230. data/test/fixtures/test/list.erb +1 -0
  231. data/test/fixtures/test/{non_erb_block_content_for.rxml → non_erb_block_content_for.builder} +0 -0
  232. data/test/fixtures/test/{potential_conflicts.rhtml → potential_conflicts.erb} +0 -0
  233. data/test/fixtures/test/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  234. data/test/fixtures/test/{render_file_with_locals.rhtml → render_file_with_locals.erb} +0 -0
  235. data/test/fixtures/test/{render_to_string_test.rhtml → render_to_string_test.erb} +0 -0
  236. data/test/fixtures/test/{update_element_with_capture.rhtml → update_element_with_capture.erb} +0 -0
  237. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  238. data/test/fixtures/topic.rb +1 -1
  239. data/test/template/active_record_helper_test.rb +67 -20
  240. data/test/template/asset_tag_helper_test.rb +222 -54
  241. data/test/template/atom_feed_helper_test.rb +101 -0
  242. data/test/template/benchmark_helper_test.rb +2 -2
  243. data/test/template/compiled_templates_test.rb +76 -32
  244. data/test/template/date_helper_test.rb +125 -9
  245. data/test/template/form_helper_test.rb +326 -33
  246. data/test/template/form_options_helper_test.rb +822 -15
  247. data/test/template/form_tag_helper_test.rb +96 -30
  248. data/test/template/javascript_helper_test.rb +61 -13
  249. data/test/template/number_helper_test.rb +12 -11
  250. data/test/template/prototype_helper_test.rb +185 -24
  251. data/test/template/sanitize_helper_test.rb +49 -0
  252. data/test/template/scriptaculous_helper_test.rb +9 -3
  253. data/test/template/tag_helper_test.rb +13 -2
  254. data/test/template/text_helper_test.rb +38 -52
  255. data/test/template/url_helper_test.rb +216 -46
  256. metadata +144 -116
  257. data/examples/.htaccess +0 -24
  258. data/examples/address_book/index.rhtml +0 -33
  259. data/examples/address_book/layout.rhtml +0 -8
  260. data/examples/address_book_controller.cgi +0 -9
  261. data/examples/address_book_controller.fcgi +0 -6
  262. data/examples/address_book_controller.rb +0 -52
  263. data/examples/address_book_controller.rbx +0 -4
  264. data/examples/benchmark.rb +0 -52
  265. data/examples/benchmark_with_ar.fcgi +0 -89
  266. data/examples/blog_controller.cgi +0 -53
  267. data/examples/debate/index.rhtml +0 -14
  268. data/examples/debate/new_topic.rhtml +0 -22
  269. data/examples/debate/topic.rhtml +0 -32
  270. data/examples/debate_controller.cgi +0 -57
  271. data/lib/action_controller/assertions/deprecated_assertions.rb +0 -228
  272. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -36
  273. data/lib/action_controller/cgi_ext/cgi_methods.rb +0 -211
  274. data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +0 -30
  275. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +0 -95
  276. data/lib/action_controller/cgi_ext/session_performance_fix.rb +0 -30
  277. data/lib/action_controller/deprecated_dependencies.rb +0 -65
  278. data/lib/action_controller/deprecated_redirects.rb +0 -17
  279. data/lib/action_controller/deprecated_request_methods.rb +0 -34
  280. data/lib/action_controller/macros/auto_complete.rb +0 -53
  281. data/lib/action_controller/macros/in_place_editing.rb +0 -33
  282. data/lib/action_controller/pagination.rb +0 -408
  283. data/lib/action_controller/scaffolding.rb +0 -189
  284. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -44
  285. data/lib/action_controller/templates/scaffolds/edit.rhtml +0 -7
  286. data/lib/action_controller/templates/scaffolds/layout.rhtml +0 -69
  287. data/lib/action_controller/templates/scaffolds/list.rhtml +0 -27
  288. data/lib/action_controller/templates/scaffolds/new.rhtml +0 -6
  289. data/lib/action_controller/templates/scaffolds/show.rhtml +0 -9
  290. data/lib/action_controller/vendor/xml_node.rb +0 -97
  291. data/lib/action_view/helpers/deprecated_helper.rb +0 -37
  292. data/lib/action_view/helpers/java_script_macros_helper.rb +0 -233
  293. data/lib/action_view/helpers/pagination_helper.rb +0 -86
  294. data/test/activerecord/active_record_assertions_test.rb +0 -92
  295. data/test/activerecord/pagination_test.rb +0 -165
  296. data/test/controller/deprecated_instance_variables_test.rb +0 -48
  297. data/test/controller/raw_post_test.rb +0 -68
  298. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +0 -1
  299. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +0 -1
  300. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +0 -1
  301. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +0 -1
  302. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +0 -1
  303. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +0 -1
  304. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +0 -1
  305. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +0 -1
  306. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +0 -1
  307. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +0 -1
  308. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +0 -1
  309. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +0 -1
  310. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +0 -1
  311. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +0 -1
  312. data/test/fixtures/respond_to/layouts/standard.rhtml +0 -1
  313. data/test/fixtures/test/_hash_object.rhtml +0 -1
  314. data/test/fixtures/test/list.rhtml +0 -1
  315. data/test/template/deprecated_helper_test.rb +0 -36
  316. data/test/template/deprecated_instance_variables_test.rb +0 -43
  317. data/test/template/java_script_macros_helper_test.rb +0 -109
@@ -1,22 +1,28 @@
1
1
  module ActionView
2
2
  module Helpers #:nodoc:
3
- # Provides methods for converting a numbers into formatted strings.
3
+ # Provides methods for converting numbers into formatted strings.
4
4
  # Methods are provided for phone numbers, currency, percentage,
5
5
  # precision, positional notation, and file size.
6
6
  module NumberHelper
7
- # Formats a +number+ into a US phone number. You can customize the format
7
+ # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
8
8
  # in the +options+ hash.
9
+ #
10
+ # ==== Options
9
11
  # * <tt>:area_code</tt> - Adds parentheses around the area code.
10
- # * <tt>:delimiter</tt> - Specifies the delimiter to use, defaults to "-".
12
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use (defaults to "-").
11
13
  # * <tt>:extension</tt> - Specifies an extension to add to the end of the
12
- # generated number
14
+ # generated number.
13
15
  # * <tt>:country_code</tt> - Sets the country code for the phone number.
14
16
  #
15
- # number_to_phone(1235551234) => 123-555-1234
16
- # number_to_phone(1235551234, :area_code => true) => (123) 555-1234
17
- # number_to_phone(1235551234, :delimiter => " ") => 123 555 1234
18
- # number_to_phone(1235551234, :area_code => true, :extension => 555) => (123) 555-1234 x 555
19
- # number_to_phone(1235551234, :country_code => 1)
17
+ # ==== Examples
18
+ # number_to_phone(1235551234) # => 123-555-1234
19
+ # number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
20
+ # number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
21
+ # number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
22
+ # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
23
+ #
24
+ # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
25
+ # => +1.123.555.1234 x 1343
20
26
  def number_to_phone(number, options = {})
21
27
  number = number.to_s.strip unless number.nil?
22
28
  options = options.stringify_keys
@@ -40,25 +46,29 @@ module ActionView
40
46
  end
41
47
  end
42
48
 
43
- # Formats a +number+ into a currency string. You can customize the format
49
+ # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
44
50
  # in the +options+ hash.
45
- # * <tt>:precision</tt> - Sets the level of precision, defaults to 2
46
- # * <tt>:unit</tt> - Sets the denomination of the currency, defaults to "$"
47
- # * <tt>:separator</tt> - Sets the separator between the units, defaults to "."
48
- # * <tt>:delimiter</tt> - Sets the thousands delimiter, defaults to ","
49
51
  #
50
- # number_to_currency(1234567890.50) => $1,234,567,890.50
51
- # number_to_currency(1234567890.506) => $1,234,567,890.51
52
- # number_to_currency(1234567890.506, :precision => 3) => $1,234,567,890.506
53
- # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
54
- # => &pound;1234567890,50
52
+ # ==== Options
53
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
54
+ # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
55
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
56
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
57
+ #
58
+ # ==== Examples
59
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
60
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
61
+ # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
62
+ #
63
+ # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
64
+ # # => &pound;1234567890,50
55
65
  def number_to_currency(number, options = {})
56
66
  options = options.stringify_keys
57
67
  precision = options["precision"] || 2
58
68
  unit = options["unit"] || "$"
59
69
  separator = precision > 0 ? options["separator"] || "." : ""
60
70
  delimiter = options["delimiter"] || ","
61
-
71
+
62
72
  begin
63
73
  parts = number_with_precision(number, precision).split('.')
64
74
  unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
@@ -67,19 +77,24 @@ module ActionView
67
77
  end
68
78
  end
69
79
 
70
- # Formats a +number+ as a percentage string. You can customize the
80
+ # Formats a +number+ as a percentage string (e.g., 65%). You can customize the
71
81
  # format in the +options+ hash.
72
- # * <tt>:precision</tt> - Sets the level of precision, defaults to 3
73
- # * <tt>:separator</tt> - Sets the separator between the units, defaults to "."
74
82
  #
75
- # number_to_percentage(100) => 100.000%
76
- # number_to_percentage(100, {:precision => 0}) => 100%
77
- # number_to_percentage(302.0574, {:precision => 2}) => 302.06%
83
+ # ==== Options
84
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
85
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
86
+ #
87
+ # ==== Examples
88
+ # number_to_percentage(100) # => 100.000%
89
+ # number_to_percentage(100, :precision => 0) # => 100%
90
+ #
91
+ # number_to_percentage(302.24398923423, :precision => 5)
92
+ # # => 302.24399%
78
93
  def number_to_percentage(number, options = {})
79
94
  options = options.stringify_keys
80
95
  precision = options["precision"] || 3
81
96
  separator = options["separator"] || "."
82
-
97
+
83
98
  begin
84
99
  number = number_with_precision(number, precision)
85
100
  parts = number.split('.')
@@ -93,14 +108,20 @@ module ActionView
93
108
  end
94
109
  end
95
110
 
96
- # Formats a +number+ with grouped thousands using +delimiter+. You
111
+ # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
97
112
  # can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
98
- # * <tt>delimiter</tt> - Sets the thousands delimiter, defaults to ","
99
- # * <tt>separator</tt> - Sets the separator between the units, defaults to "."
100
113
  #
101
- # number_with_delimiter(12345678) => 12,345,678
102
- # number_with_delimiter(12345678.05) => 12,345,678.05
103
- # number_with_delimiter(12345678, ".") => 12.345.678
114
+ # ==== Options
115
+ # * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
116
+ # * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
117
+ #
118
+ # ==== Examples
119
+ # number_with_delimiter(12345678) # => 12,345,678
120
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
121
+ # number_with_delimiter(12345678, ".") # => 12.345.678
122
+ #
123
+ # number_with_delimiter(98765432.98, " ", ",")
124
+ # # => 98 765 432,98
104
125
  def number_with_delimiter(number, delimiter=",", separator=".")
105
126
  begin
106
127
  parts = number.to_s.split('.')
@@ -110,46 +131,49 @@ module ActionView
110
131
  number
111
132
  end
112
133
  end
113
-
114
- # Formats a +number+ with the specified level of +precision+. The default
134
+
135
+ # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
115
136
  # level of precision is 3.
116
137
  #
117
- # number_with_precision(111.2345) => 111.235
118
- # number_with_precision(111.2345, 2) => 111.24
138
+ # ==== Examples
139
+ # number_with_precision(111.2345) # => 111.235
140
+ # number_with_precision(111.2345, 2) # => 111.24
141
+ # number_with_precision(13, 5) # => 13.00000
142
+ # number_with_precision(389.32314, 0) # => 389
119
143
  def number_with_precision(number, precision=3)
120
144
  "%01.#{precision}f" % number
121
145
  rescue
122
146
  number
123
147
  end
124
-
125
- # Formats the bytes in +size+ into a more understandable representation.
126
- # Useful for reporting file sizes to users. This method returns nil if
127
- # +size+ cannot be converted into a number. You can change the default
128
- # precision of 1 in +precision+.
129
- #
130
- # number_to_human_size(123) => 123 Bytes
131
- # number_to_human_size(1234) => 1.2 KB
132
- # number_to_human_size(12345) => 12.1 KB
133
- # number_to_human_size(1234567) => 1.2 MB
134
- # number_to_human_size(1234567890) => 1.1 GB
135
- # number_to_human_size(1234567890123) => 1.1 TB
136
- # number_to_human_size(1234567, 2) => 1.18 MB
148
+
149
+ # Formats the bytes in +size+ into a more understandable representation
150
+ # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
151
+ # reporting file sizes to users. This method returns nil if
152
+ # +size+ cannot be converted into a number. You can change the default
153
+ # precision of 1 using the precision parameter +precision+.
154
+ #
155
+ # ==== Examples
156
+ # number_to_human_size(123) # => 123 Bytes
157
+ # number_to_human_size(1234) # => 1.2 KB
158
+ # number_to_human_size(12345) # => 12.1 KB
159
+ # number_to_human_size(1234567) # => 1.2 MB
160
+ # number_to_human_size(1234567890) # => 1.1 GB
161
+ # number_to_human_size(1234567890123) # => 1.1 TB
162
+ # number_to_human_size(1234567, 2) # => 1.18 MB
163
+ # number_to_human_size(483989, 0) # => 4 MB
137
164
  def number_to_human_size(size, precision=1)
138
165
  size = Kernel.Float(size)
139
- case
140
- when size == 1 : "1 Byte"
141
- when size < 1.kilobyte: "%d Bytes" % size
142
- when size < 1.megabyte: "%.#{precision}f KB" % (size / 1.0.kilobyte)
143
- when size < 1.gigabyte: "%.#{precision}f MB" % (size / 1.0.megabyte)
144
- when size < 1.terabyte: "%.#{precision}f GB" % (size / 1.0.gigabyte)
166
+ case
167
+ when size.to_i == 1; "1 Byte"
168
+ when size < 1.kilobyte; "%d Bytes" % size
169
+ when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
170
+ when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
171
+ when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
145
172
  else "%.#{precision}f TB" % (size / 1.0.terabyte)
146
- end.sub('.0', '')
173
+ end.sub(/([0-9])\.?0+ /, '\1 ' )
147
174
  rescue
148
175
  nil
149
176
  end
150
-
151
- alias_method :human_size, :number_to_human_size # deprecated alias
152
- deprecate :human_size => :number_to_human_size
153
177
  end
154
178
  end
155
179
  end
@@ -2,25 +2,106 @@ require 'set'
2
2
 
3
3
  module ActionView
4
4
  module Helpers
5
- # Provides a set of helpers for calling Prototype JavaScript functions,
6
- # including functionality to call remote methods using
7
- # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php].
5
+ # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
6
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
7
+ # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
8
+ # functionality, and more traditional object-oriented facilities for JavaScript.
9
+ # This module provides a set of helpers to make it more convenient to call
10
+ # functions from Prototype using Rails, including functionality to call remote
11
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
8
12
  # This means that you can call actions in your controllers without
9
13
  # reloading the page, but still update certain parts of it using
10
- # injections into the DOM. The common use case is having a form that adds
11
- # a new element to a list without reloading the page.
14
+ # injections into the DOM. A common use case is having a form that adds
15
+ # a new element to a list without reloading the page or updating a shopping
16
+ # cart total when a new item is added.
12
17
  #
13
- # To be able to use these helpers, you must include the Prototype
14
- # JavaScript framework in your pages. See the documentation for
15
- # ActionView::Helpers::JavaScriptHelper for more information on including
16
- # the necessary JavaScript.
18
+ # == Usage
19
+ # To be able to use these helpers, you must first include the Prototype
20
+ # JavaScript framework in your pages.
17
21
  #
22
+ # javascript_include_tag 'prototype'
23
+ #
24
+ # (See the documentation for
25
+ # ActionView::Helpers::JavaScriptHelper for more information on including
26
+ # this and other JavaScript files in your Rails templates.)
27
+ #
28
+ # Now you're ready to call a remote action either through a link...
29
+ #
30
+ # link_to_remote "Add to cart",
31
+ # :url => { :action => "add", :id => product.id },
32
+ # :update => { :success => "cart", :failure => "error" }
33
+ #
34
+ # ...through a form...
35
+ #
36
+ # <% form_remote_tag :url => '/shipping' do -%>
37
+ # <div><%= submit_tag 'Recalculate Shipping' %></div>
38
+ # <% end -%>
39
+ #
40
+ # ...periodically...
41
+ #
42
+ # periodically_call_remote(:url => 'update', :frequency => '5', :update => 'ticker')
43
+ #
44
+ # ...or through an observer (i.e., a form or field that is observed and calls a remote
45
+ # action when changed).
46
+ #
47
+ # <%= observe_field(:searchbox,
48
+ # :url => { :action => :live_search }),
49
+ # :frequency => 0.5,
50
+ # :update => :hits,
51
+ # :with => 'query'
52
+ # %>
53
+ #
54
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
55
+ # are listed here); check out the documentation for each method to find out more about its usage and options.
56
+ #
57
+ # === Common Options
18
58
  # See link_to_remote for documentation of options common to all Ajax
19
- # helpers.
59
+ # helpers; any of the options specified by link_to_remote can be used
60
+ # by the other helpers.
61
+ #
62
+ # == Designing your Rails actions for Ajax
63
+ # When building your action handlers (that is, the Rails actions that receive your background requests), it's
64
+ # important to remember a few things. First, whatever your action would normall return to the browser, it will
65
+ # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
66
+ # 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.
67
+ # You can turn the layout off on particular actions by doing the following:
68
+ #
69
+ # class SiteController < ActionController::Base
70
+ # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
71
+ # end
72
+ #
73
+ # Optionally, you could do this in the method you wish to lack a layout:
74
+ #
75
+ # render :layout => false
76
+ #
77
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
78
+ # method that Ajax uses to make background requests) method.
79
+ # def name
80
+ # # Is this an XmlHttpRequest request?
81
+ # if (request.xhr?)
82
+ # render :text => @name.to_s
83
+ # else
84
+ # # No? Then render an action.
85
+ # render :action => 'view_attribute', :attr => @name
86
+ # end
87
+ # end
20
88
  #
21
- # See also ActionView::Helpers::ScriptaculousHelper for helpers which work
22
- # with the Scriptaculous controls and visual effects library.
89
+ # The else clause can be left off and the current action will render with full layout and template. An extension
90
+ # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
23
91
  #
92
+ # layout proc{ |c| c.request.xhr? ? false : "application" }
93
+ #
94
+ # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
95
+ #
96
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
97
+ # render text output, like this:
98
+ #
99
+ # render :text => 'Return this from my method!'
100
+ #
101
+ # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
102
+ # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
103
+ #
104
+ # == Updating multiple elements
24
105
  # See JavaScriptGenerator for information on updating multiple elements
25
106
  # on the page in an Ajax response.
26
107
  module PrototypeHelper
@@ -41,16 +122,30 @@ module ActionView
41
122
  # render :partial.
42
123
  #
43
124
  # Examples:
125
+ # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
126
+ # # return false;">Delete this post</a>
44
127
  # link_to_remote "Delete this post", :update => "posts",
45
128
  # :url => { :action => "destroy", :id => post.id }
129
+ #
130
+ # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
131
+ # # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
46
132
  # link_to_remote(image_tag("refresh"), :update => "emails",
47
133
  # :url => { :action => "list_emails" })
134
+ #
135
+ # You can override the generated HTML options by specifying a hash in
136
+ # <tt>options[:html]</tt>.
137
+ #
138
+ # link_to_remote "Delete this post", :update => "posts",
139
+ # :url => post_url(@post), :method => :delete,
140
+ # :html => { :class => "destructive" }
48
141
  #
49
142
  # You can also specify a hash for <tt>options[:update]</tt> to allow for
50
143
  # easy redirection of output to an other DOM element if a server-side
51
144
  # error occurs:
52
145
  #
53
146
  # Example:
147
+ # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
148
+ # # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
54
149
  # link_to_remote "Delete this post",
55
150
  # :url => { :action => "destroy", :id => post.id },
56
151
  # :update => { :success => "posts", :failure => "error" }
@@ -63,6 +158,8 @@ module ActionView
63
158
  # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
64
159
  #
65
160
  # Example:
161
+ # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
162
+ # # return false;">Destroy</a>
66
163
  # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
67
164
  #
68
165
  # By default, these remote requests are processed asynchronous during
@@ -74,6 +171,9 @@ module ActionView
74
171
  # find out the HTTP status, use <tt>request.status</tt>.
75
172
  #
76
173
  # Example:
174
+ # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
175
+ # # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
176
+ # word = 'hello'
77
177
  # link_to_remote word,
78
178
  # :url => { :action => "undo", :n => word_counter },
79
179
  # :complete => "undoRequestCompleted(request)"
@@ -100,6 +200,9 @@ module ActionView
100
200
  # adding additional callbacks for specific status codes.
101
201
  #
102
202
  # Example:
203
+ # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
204
+ # # on404:function(request){alert('Not found...? Wrong URL...?')},
205
+ # # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
103
206
  # link_to_remote word,
104
207
  # :url => { :action => "action" },
105
208
  # 404 => "alert('Not found...? Wrong URL...?')",
@@ -129,8 +232,27 @@ module ActionView
129
232
  # default this is the current form, but
130
233
  # it could just as well be the ID of a
131
234
  # table row or any other DOM element.
132
- def link_to_remote(name, options = {}, html_options = {})
133
- link_to_function(name, remote_function(options), html_options)
235
+ # <tt>:with</tt>:: A JavaScript expression specifying
236
+ # the parameters for the XMLHttpRequest.
237
+ # Any expressions should return a valid
238
+ # URL query string.
239
+ #
240
+ # Example:
241
+ #
242
+ # :with => "'name=' + $('name').value"
243
+ #
244
+ # You can generate a link that uses AJAX in the general case, while
245
+ # degrading gracefully to plain link behavior in the absence of
246
+ # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
247
+ # Note the extra curly braces around the <tt>options</tt> hash separate
248
+ # it as the second parameter from <tt>html_options</tt>, the third.
249
+ #
250
+ # Example:
251
+ # link_to_remote "Delete this post",
252
+ # { :update => "posts", :url => { :action => "destroy", :id => post.id } },
253
+ # :href => url_for(:action => "destroy", :id => post.id)
254
+ def link_to_remote(name, options = {}, html_options = nil)
255
+ link_to_function(name, remote_function(options), html_options || options.delete(:html))
134
256
  end
135
257
 
136
258
  # Periodically calls the specified url (<tt>options[:url]</tt>) every
@@ -138,6 +260,26 @@ module ActionView
138
260
  # update a specified div (<tt>options[:update]</tt>) with the results
139
261
  # of the remote call. The options for specifying the target with :url
140
262
  # and defining callbacks is the same as link_to_remote.
263
+ # Examples:
264
+ # # Call get_averages and put its results in 'avg' every 10 seconds
265
+ # # Generates:
266
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
267
+ # # {asynchronous:true, evalScripts:true})}, 10)
268
+ # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
269
+ #
270
+ # # Call invoice every 10 seconds with the id of the customer
271
+ # # If it succeeds, update the invoice DIV; if it fails, update the error DIV
272
+ # # Generates:
273
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
274
+ # # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
275
+ # periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
276
+ # :update => { :success => "invoice", :failure => "error" }
277
+ #
278
+ # # Call update every 20 seconds and update the new_block DIV
279
+ # # Generates:
280
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20)
281
+ # periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
282
+ #
141
283
  def periodically_call_remote(options = {})
142
284
  frequency = options[:frequency] || 10 # every ten seconds by default
143
285
  code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
@@ -156,6 +298,9 @@ module ActionView
156
298
  # specified with the :action/:method options on :html.
157
299
  #
158
300
  # Example:
301
+ # # Generates:
302
+ # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
303
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
159
304
  # form_remote_tag :html => { :action =>
160
305
  # url_for(:controller => "some", :action => "place") }
161
306
  #
@@ -166,6 +311,11 @@ module ActionView
166
311
  # the :url (and the default method is :post).
167
312
  #
168
313
  # form_remote_tag also takes a block, like form_tag:
314
+ # # Generates:
315
+ # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
316
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
317
+ # # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
318
+ # # </form>
169
319
  # <% form_remote_tag :url => '/posts' do -%>
170
320
  # <div><%= submit_tag 'Save' %></div>
171
321
  # <% end -%>
@@ -180,18 +330,85 @@ module ActionView
180
330
  form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
181
331
  end
182
332
 
183
- # Works like form_remote_tag, but uses form_for semantics.
184
- def remote_form_for(object_name, *args, &proc)
185
- options = args.last.is_a?(Hash) ? args.pop : {}
333
+ # Creates a form that will submit using XMLHttpRequest in the background
334
+ # instead of the regular reloading POST arrangement and a scope around a
335
+ # specific resource that is used as a base for questioning about
336
+ # values for the fields.
337
+ #
338
+ # === Resource
339
+ #
340
+ # Example:
341
+ # <% remote_form_for(@post) do |f| %>
342
+ # ...
343
+ # <% end %>
344
+ #
345
+ # This will expand to be the same as:
346
+ #
347
+ # <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
348
+ # ...
349
+ # <% end %>
350
+ #
351
+ # === Nested Resource
352
+ #
353
+ # Example:
354
+ # <% remote_form_for([@post, @comment]) do |f| %>
355
+ # ...
356
+ # <% end %>
357
+ #
358
+ # This will expand to be the same as:
359
+ #
360
+ # <% remote_form_for :comment, @comment, :url => post_comment_path(@post, @comment), :html => { :method => :put, :class => "edit_comment", :id => "edit_comment_45" } do |f| %>
361
+ # ...
362
+ # <% end %>
363
+ #
364
+ # If you don't need to attach a form to a resource, then check out form_remote_tag.
365
+ #
366
+ # See FormHelper#form_for for additional semantics.
367
+ def remote_form_for(record_or_name_or_array, *args, &proc)
368
+ options = args.extract_options!
369
+
370
+ case record_or_name_or_array
371
+ when String, Symbol
372
+ object_name = record_or_name_or_array
373
+ when Array
374
+ object = record_or_name_or_array.last
375
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
376
+ apply_form_for_options!(record_or_name_or_array, options)
377
+ args.unshift object
378
+ else
379
+ object = record_or_name_or_array
380
+ object_name = ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array)
381
+ apply_form_for_options!(object, options)
382
+ args.unshift object
383
+ end
384
+
186
385
  concat(form_remote_tag(options), proc.binding)
187
386
  fields_for(object_name, *(args << options), &proc)
188
387
  concat('</form>', proc.binding)
189
388
  end
190
389
  alias_method :form_remote_for, :remote_form_for
191
390
 
192
- # Returns a button input tag that will submit form using XMLHttpRequest
193
- # in the background instead of regular reloading POST arrangement.
194
- # <tt>options</tt> argument is the same as in <tt>form_remote_tag</tt>.
391
+ # Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
392
+ # that will submit form using XMLHttpRequest in the background instead of a regular POST request that
393
+ # reloads the page.
394
+ #
395
+ # # Create a button that submits to the create action
396
+ # #
397
+ # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
398
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
399
+ # # return false;" type="button" value="Create" />
400
+ # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
401
+ #
402
+ # # Submit to the remote action update and update the DIV succeed or fail based
403
+ # # on the success or failure of the request
404
+ # #
405
+ # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
406
+ # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
407
+ # # return false;" type="button" value="Update" />
408
+ # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
409
+ # :update => { :success => "succeed", :failure => "fail" }
410
+ #
411
+ # <tt>options</tt> argument is the same as in form_remote_tag.
195
412
  def submit_to_remote(name, value, options = {})
196
413
  options[:with] ||= 'Form.serialize(this.form)'
197
414
 
@@ -204,7 +421,7 @@ module ActionView
204
421
  tag("input", options[:html], false)
205
422
  end
206
423
 
207
- # Returns 'eval(request.responseText)' which is the JavaScript function
424
+ # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
208
425
  # that form_remote_tag can call in :complete to evaluate a multiple
209
426
  # update return document using update_element_function calls.
210
427
  def evaluate_remote_response
@@ -215,6 +432,8 @@ module ActionView
215
432
  # Takes the same arguments as link_to_remote.
216
433
  #
217
434
  # Example:
435
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
436
+ # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
218
437
  # <select id="options" onchange="<%= remote_function(:update => "options",
219
438
  # :url => { :action => :update_options }) %>">
220
439
  # <option value="0">Hello</option>
@@ -255,11 +474,29 @@ module ActionView
255
474
  # Ajax call. By default the value of the observed field is sent as a
256
475
  # parameter with the Ajax call.
257
476
  #
477
+ # Example:
478
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
479
+ # # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
480
+ # <%= observe_field :suggest, :url => { :action => :find_suggestion },
481
+ # :frequency => 0.25,
482
+ # :update => :suggest,
483
+ # :with => 'q'
484
+ # %>
485
+ #
258
486
  # Required +options+ are either of:
259
487
  # <tt>:url</tt>:: +url_for+-style options for the action to call
260
488
  # when the field has changed.
261
489
  # <tt>:function</tt>:: Instead of making a remote call to a URL, you
262
- # can specify a function to be called instead.
490
+ # can specify javascript code to be called instead.
491
+ # Note that the value of this option is used as the
492
+ # *body* of the javascript function, a function definition
493
+ # with parameters named element and value will be generated for you
494
+ # for example:
495
+ # observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
496
+ # will generate:
497
+ # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
498
+ # The element parameter is the DOM element being observed, and the value is its value at the
499
+ # time the observer is triggered.
263
500
  #
264
501
  # Additional options are:
265
502
  # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
@@ -294,8 +531,23 @@ module ActionView
294
531
  # you can specify it instead to be "blur" or "focus" or
295
532
  # any other event.
296
533
  #
297
- # Additionally, you may specify any of the options documented in
298
- # link_to_remote.
534
+ # Additionally, you may specify any of the options documented in the
535
+ # <em>Common options</em> section at the top of this document.
536
+ #
537
+ # Example:
538
+ #
539
+ # # Sends params: {:title => 'Title of the book'} when the book_title input
540
+ # # field is changed.
541
+ # observe_field 'book_title',
542
+ # :url => 'http://example.com/books/edit/1',
543
+ # :with => 'title'
544
+ #
545
+ # # Sends params: {:book_title => 'Title of the book'} when the focus leaves
546
+ # # the input field.
547
+ # observe_field 'book_title',
548
+ # :url => 'http://example.com/books/edit/1',
549
+ # :on => 'blur'
550
+ #
299
551
  def observe_field(field_id, options = {})
300
552
  if options[:frequency] && options[:frequency] > 0
301
553
  build_observer('Form.Element.Observer', field_id, options)
@@ -350,17 +602,16 @@ module ActionView
350
602
  #
351
603
  # Example:
352
604
  #
605
+ # # Generates:
606
+ # # new Insertion.Bottom("list", "<li>Some item</li>");
607
+ # # new Effect.Highlight("list");
608
+ # # ["status-indicator", "cancel-link"].each(Element.hide);
353
609
  # update_page do |page|
354
610
  # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
355
611
  # page.visual_effect :highlight, 'list'
356
612
  # page.hide 'status-indicator', 'cancel-link'
357
613
  # end
358
614
  #
359
- # generates the following JavaScript:
360
- #
361
- # new Insertion.Bottom("list", "<li>Some item</li>");
362
- # new Effect.Highlight("list");
363
- # ["status-indicator", "cancel-link"].each(Element.hide);
364
615
  #
365
616
  # Helper methods can be used in conjunction with JavaScriptGenerator.
366
617
  # When a helper method is called inside an update block on the +page+
@@ -400,8 +651,19 @@ module ActionView
400
651
  # page['blank_slate'] # => $('blank_slate');
401
652
  # page['blank_slate'].show # => $('blank_slate').show();
402
653
  # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
654
+ #
655
+ # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
656
+ # the correct id:
657
+ #
658
+ # page[@post] # => $('post_45')
659
+ # page[Post.new] # => $('new_post')
403
660
  def [](id)
404
- JavaScriptElementProxy.new(self, id)
661
+ case id
662
+ when String, Symbol, NilClass
663
+ JavaScriptElementProxy.new(self, id)
664
+ else
665
+ JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
666
+ end
405
667
  end
406
668
 
407
669
  # Returns an object whose <tt>#to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
@@ -419,18 +681,19 @@ module ActionView
419
681
  #
420
682
  # You can also use prototype enumerations with the collection. Observe:
421
683
  #
684
+ # # Generates: $$('#items li').each(function(value) { value.hide(); });
422
685
  # page.select('#items li').each do |value|
423
686
  # value.hide
424
687
  # end
425
- # # => $$('#items li').each(function(value) { value.hide(); });
426
688
  #
427
689
  # Though you can call the block param anything you want, they are always rendered in the
428
690
  # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
429
- #
691
+ #
692
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
430
693
  # page.select('#items li').collect('hidden') do |item|
431
694
  # item.hide
432
695
  # end
433
- # # => var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
696
+ #
434
697
  def select(pattern)
435
698
  JavaScriptElementCollectionProxy.new(self, pattern)
436
699
  end
@@ -444,7 +707,7 @@ module ActionView
444
707
  # element's existing content.
445
708
  # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
446
709
  # element's existing content.
447
- # <tt>:before</tt>:: HTML is inserted immediately preceeding the element.
710
+ # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
448
711
  # <tt>:after</tt>:: HTML is inserted immediately following the element.
449
712
  #
450
713
  # +options_for_render+ may be either a string of HTML to insert, or a hash
@@ -452,9 +715,11 @@ module ActionView
452
715
  #
453
716
  # # Insert the rendered 'navigation' partial just before the DOM
454
717
  # # element with ID 'content'.
718
+ # # Generates: new Insertion.Before("content", "-- Contents of 'navigation' partial --");
455
719
  # insert_html :before, 'content', :partial => 'navigation'
456
720
  #
457
721
  # # Add a list item to the bottom of the <ul> with ID 'list'.
722
+ # # Generates: new Insertion.Bottom("list", "<li>Last item</li>");
458
723
  # insert_html :bottom, 'list', '<li>Last item</li>'
459
724
  #
460
725
  def insert_html(position, id, *options_for_render)
@@ -469,6 +734,7 @@ module ActionView
469
734
  #
470
735
  # # Replace the HTML of the DOM element having ID 'person-45' with the
471
736
  # # 'person' partial for the appropriate object.
737
+ # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
472
738
  # replace_html 'person-45', :partial => 'person', :object => @person
473
739
  #
474
740
  def replace_html(id, *options_for_render)
@@ -496,9 +762,13 @@ module ActionView
496
762
  # </div>
497
763
  #
498
764
  # # Insert a new person
765
+ # #
766
+ # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
499
767
  # page.insert_html :bottom, :partial => 'person', :object => @person
500
768
  #
501
769
  # # Replace an existing person
770
+ #
771
+ # # Generates: Element.replace("person_45", "-- Contents of partial --");
502
772
  # page.replace 'person_45', :partial => 'person', :object => @person
503
773
  #
504
774
  def replace(id, *options_for_render)
@@ -506,31 +776,72 @@ module ActionView
506
776
  end
507
777
 
508
778
  # Removes the DOM elements with the given +ids+ from the page.
779
+ #
780
+ # Example:
781
+ #
782
+ # # Remove a few people
783
+ # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
784
+ # page.remove 'person_23', 'person_9', 'person_2'
785
+ #
509
786
  def remove(*ids)
510
787
  loop_on_multiple_args 'Element.remove', ids
511
788
  end
512
789
 
513
790
  # Shows hidden DOM elements with the given +ids+.
791
+ #
792
+ # Example:
793
+ #
794
+ # # Show a few people
795
+ # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
796
+ # page.show 'person_6', 'person_13', 'person_223'
797
+ #
514
798
  def show(*ids)
515
799
  loop_on_multiple_args 'Element.show', ids
516
800
  end
517
801
 
518
802
  # Hides the visible DOM elements with the given +ids+.
803
+ #
804
+ # Example:
805
+ #
806
+ # # Hide a few people
807
+ # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
808
+ # page.hide 'person_29', 'person_9', 'person_0'
809
+ #
519
810
  def hide(*ids)
520
811
  loop_on_multiple_args 'Element.hide', ids
521
812
  end
522
813
 
523
814
  # Toggles the visibility of the DOM elements with the given +ids+.
815
+ # Example:
816
+ #
817
+ # # Show a few people
818
+ # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
819
+ # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
820
+ # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
821
+ #
524
822
  def toggle(*ids)
525
823
  loop_on_multiple_args 'Element.toggle', ids
526
824
  end
527
825
 
528
826
  # Displays an alert dialog with the given +message+.
827
+ #
828
+ # Example:
829
+ #
830
+ # # Generates: alert('This message is from Rails!')
831
+ # page.alert('This message is from Rails!')
529
832
  def alert(message)
530
833
  call 'alert', message
531
834
  end
532
835
 
533
- # Redirects the browser to the given +location+, in the same form as +url_for+.
836
+ # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
837
+ #
838
+ # Examples:
839
+ #
840
+ # # Generates: window.location.href = "/mycontroller";
841
+ # page.redirect_to(:action => 'index')
842
+ #
843
+ # # Generates: window.location.href = "/account/signup";
844
+ # page.redirect_to(:controller => 'account', :action => 'signup')
534
845
  def redirect_to(location)
535
846
  assign 'window.location.href', @context.url_for(location)
536
847
  end
@@ -540,22 +851,52 @@ module ActionView
540
851
  # If a block is given, the block will be passed to a new JavaScriptGenerator;
541
852
  # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
542
853
  # and passed as the called function's final argument.
854
+ #
855
+ # Examples:
856
+ #
857
+ # # Generates: Element.replace(my_element, "My content to replace with.")
858
+ # page.call 'Element.replace', 'my_element', "My content to replace with."
859
+ #
860
+ # # Generates: alert('My message!')
861
+ # page.call 'alert', 'My message!'
862
+ #
543
863
  def call(function, *arguments, &block)
544
864
  record "#{function}(#{arguments_for_call(arguments, block)})"
545
865
  end
546
866
 
547
867
  # Assigns the JavaScript +variable+ the given +value+.
868
+ #
869
+ # Examples:
870
+ #
871
+ # # Generates: my_string = "This is mine!";
872
+ # page.assign 'my_string', 'This is mine!'
873
+ #
874
+ # # Generates: record_count = 33;
875
+ # page.assign 'record_count', 33
876
+ #
877
+ # # Generates: tabulated_total = 47
878
+ # page.assign 'tabulated_total', @total_from_cart
879
+ #
548
880
  def assign(variable, value)
549
881
  record "#{variable} = #{javascript_object_for(value)}"
550
882
  end
551
883
 
552
884
  # Writes raw JavaScript to the page.
885
+ #
886
+ # Example:
887
+ #
888
+ # page << "alert('JavaScript with Prototype.');"
553
889
  def <<(javascript)
554
890
  @lines << javascript
555
891
  end
556
892
 
557
893
  # Executes the content of the block after a delay of +seconds+. Example:
558
894
  #
895
+ # # Generates:
896
+ # # setTimeout(function() {
897
+ # # ;
898
+ # # new Effect.Fade("notice",{});
899
+ # # }, 20000);
559
900
  # page.delay(20) do
560
901
  # page.visual_effect :fade, 'notice'
561
902
  # end
@@ -609,9 +950,13 @@ module ActionView
609
950
  end
610
951
 
611
952
  def render(*options_for_render)
953
+ old_format = @context && @context.template_format
954
+ @context.template_format = :html if @context
612
955
  Hash === options_for_render.first ?
613
956
  @context.render(*options_for_render) :
614
957
  options_for_render.first.to_s
958
+ ensure
959
+ @context.template_format = old_format if @context
615
960
  end
616
961
 
617
962
  def javascript_object_for(object)
@@ -637,6 +982,12 @@ module ActionView
637
982
  # Yields a JavaScriptGenerator and returns the generated JavaScript code.
638
983
  # Use this to update multiple elements on a page in an Ajax response.
639
984
  # See JavaScriptGenerator for more information.
985
+ #
986
+ # Example:
987
+ #
988
+ # update_page do |page|
989
+ # page.hide 'spinner'
990
+ # end
640
991
  def update_page(&block)
641
992
  JavaScriptGenerator.new(@template, &block).to_s
642
993
  end
@@ -667,6 +1018,15 @@ module ActionView
667
1018
  elsif options[:with]
668
1019
  js_options['parameters'] = options[:with]
669
1020
  end
1021
+
1022
+ if protect_against_forgery?
1023
+ if js_options['parameters']
1024
+ js_options['parameters'] << " + '&"
1025
+ else
1026
+ js_options['parameters'] = "'"
1027
+ end
1028
+ js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
1029
+ end
670
1030
 
671
1031
  options_for_javascript(js_options)
672
1032
  end
@@ -676,7 +1036,7 @@ module ActionView
676
1036
  end
677
1037
 
678
1038
  def build_observer(klass, name, options = {})
679
- if options[:with] && (options[:with] !~ /[=(.]/)
1039
+ if options[:with] && (options[:with] !~ /[\{=(.]/)
680
1040
  options[:with] = "'#{options[:with]}=' + value"
681
1041
  else
682
1042
  options[:with] ||= 'value' unless options[:function]
@@ -705,7 +1065,7 @@ module ActionView
705
1065
  end
706
1066
 
707
1067
  # Converts chained method calls on DOM proxy elements into JavaScript chains
708
- class JavaScriptProxy < Builder::BlankSlate #:nodoc:
1068
+ class JavaScriptProxy < BasicObject #:nodoc:
709
1069
  def initialize(generator, root = nil)
710
1070
  @generator = generator
711
1071
  @generator << root if root
@@ -787,7 +1147,7 @@ module ActionView
787
1147
  true
788
1148
  end
789
1149
 
790
- def to_json
1150
+ def to_json(options = nil)
791
1151
  @variable
792
1152
  end
793
1153
 
@@ -800,7 +1160,7 @@ module ActionView
800
1160
  end
801
1161
 
802
1162
  class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
803
- ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by] unless defined? ENUMERABLE_METHODS_WITH_RETURN
1163
+ 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
804
1164
  ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
805
1165
  attr_reader :generator
806
1166
  delegate :arguments_for_call, :to => :generator
@@ -808,11 +1168,27 @@ module ActionView
808
1168
  def initialize(generator, pattern)
809
1169
  super(generator, @pattern = pattern)
810
1170
  end
811
-
1171
+
1172
+ def each_slice(variable, number, &block)
1173
+ if block
1174
+ enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
1175
+ else
1176
+ add_variable_assignment!(variable)
1177
+ append_enumerable_function!("eachSlice(#{number.to_json});")
1178
+ end
1179
+ end
1180
+
812
1181
  def grep(variable, pattern, &block)
813
1182
  enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
814
1183
  end
815
-
1184
+
1185
+ def in_groups_of(variable, number, fill_with = nil)
1186
+ arguments = [number]
1187
+ arguments << fill_with unless fill_with.nil?
1188
+ add_variable_assignment!(variable)
1189
+ append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
1190
+ end
1191
+
816
1192
  def inject(variable, memo, &block)
817
1193
  enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
818
1194
  end
@@ -860,7 +1236,7 @@ module ActionView
860
1236
  add_variable_assignment!(options[:variable]) if options[:variable]
861
1237
  append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
862
1238
  # only yield as many params as were passed in the block
863
- yield *options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1]
1239
+ yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
864
1240
  add_return_statement! if options[:return]
865
1241
  @generator << '});'
866
1242
  end
@@ -889,4 +1265,4 @@ module ActionView
889
1265
  end
890
1266
  end
891
1267
 
892
- require File.dirname(__FILE__) + '/javascript_helper'
1268
+ require 'action_view/helpers/javascript_helper'