actionpack 1.9.1 → 1.10.1

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 (123) hide show
  1. data/CHANGELOG +237 -0
  2. data/README +12 -12
  3. data/lib/action_controller.rb +17 -12
  4. data/lib/action_controller/assertions.rb +119 -67
  5. data/lib/action_controller/base.rb +184 -102
  6. data/lib/action_controller/benchmarking.rb +35 -6
  7. data/lib/action_controller/caching.rb +115 -58
  8. data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
  9. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
  10. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
  11. data/lib/action_controller/cgi_process.rb +23 -20
  12. data/lib/action_controller/components.rb +11 -2
  13. data/lib/action_controller/dependencies.rb +0 -5
  14. data/lib/action_controller/deprecated_redirects.rb +17 -0
  15. data/lib/action_controller/filters.rb +13 -9
  16. data/lib/action_controller/flash.rb +7 -7
  17. data/lib/action_controller/helpers.rb +1 -14
  18. data/lib/action_controller/layout.rb +40 -29
  19. data/lib/action_controller/macros/auto_complete.rb +52 -0
  20. data/lib/action_controller/macros/in_place_editing.rb +32 -0
  21. data/lib/action_controller/pagination.rb +44 -28
  22. data/lib/action_controller/request.rb +54 -40
  23. data/lib/action_controller/rescue.rb +8 -6
  24. data/lib/action_controller/routing.rb +77 -28
  25. data/lib/action_controller/scaffolding.rb +10 -14
  26. data/lib/action_controller/session/active_record_store.rb +36 -7
  27. data/lib/action_controller/session_management.rb +126 -0
  28. data/lib/action_controller/streaming.rb +14 -5
  29. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
  30. data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
  31. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
  32. data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
  33. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  34. data/lib/action_controller/test_process.rb +35 -17
  35. data/lib/action_controller/upload_progress.rb +52 -0
  36. data/lib/action_controller/url_rewriter.rb +21 -16
  37. data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
  38. data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
  39. data/lib/action_pack/version.rb +9 -0
  40. data/lib/action_view.rb +1 -1
  41. data/lib/action_view/base.rb +204 -60
  42. data/lib/action_view/compiled_templates.rb +70 -0
  43. data/lib/action_view/helpers/active_record_helper.rb +7 -3
  44. data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
  45. data/lib/action_view/helpers/capture_helper.rb +2 -10
  46. data/lib/action_view/helpers/date_helper.rb +21 -13
  47. data/lib/action_view/helpers/form_helper.rb +14 -10
  48. data/lib/action_view/helpers/form_options_helper.rb +4 -4
  49. data/lib/action_view/helpers/form_tag_helper.rb +59 -25
  50. data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
  51. data/lib/action_view/helpers/javascript_helper.rb +68 -133
  52. data/lib/action_view/helpers/javascripts/controls.js +427 -165
  53. data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
  54. data/lib/action_view/helpers/javascripts/effects.js +766 -277
  55. data/lib/action_view/helpers/javascripts/prototype.js +906 -218
  56. data/lib/action_view/helpers/javascripts/slider.js +258 -0
  57. data/lib/action_view/helpers/number_helper.rb +4 -3
  58. data/lib/action_view/helpers/pagination_helper.rb +42 -27
  59. data/lib/action_view/helpers/tag_helper.rb +25 -11
  60. data/lib/action_view/helpers/text_helper.rb +119 -13
  61. data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
  62. data/lib/action_view/helpers/url_helper.rb +68 -21
  63. data/lib/action_view/partials.rb +17 -6
  64. data/lib/action_view/template_error.rb +19 -24
  65. data/rakefile +4 -3
  66. data/test/abstract_unit.rb +2 -1
  67. data/test/controller/action_pack_assertions_test.rb +62 -2
  68. data/test/controller/active_record_assertions_test.rb +5 -6
  69. data/test/controller/active_record_store_test.rb +23 -1
  70. data/test/controller/addresses_render_test.rb +4 -0
  71. data/test/controller/{base_tests.rb → base_test.rb} +4 -3
  72. data/test/controller/benchmark_test.rb +36 -0
  73. data/test/controller/caching_filestore.rb +22 -40
  74. data/test/controller/capture_test.rb +10 -1
  75. data/test/controller/cgi_test.rb +145 -23
  76. data/test/controller/components_test.rb +50 -0
  77. data/test/controller/custom_handler_test.rb +3 -3
  78. data/test/controller/fake_controllers.rb +24 -0
  79. data/test/controller/filters_test.rb +6 -6
  80. data/test/controller/flash_test.rb +6 -6
  81. data/test/controller/fragment_store_setting_test.rb +45 -0
  82. data/test/controller/helper_test.rb +1 -3
  83. data/test/controller/new_render_test.rb +119 -7
  84. data/test/controller/redirect_test.rb +11 -1
  85. data/test/controller/render_test.rb +34 -1
  86. data/test/controller/request_test.rb +14 -5
  87. data/test/controller/routing_test.rb +238 -42
  88. data/test/controller/send_file_test.rb +11 -10
  89. data/test/controller/session_management_test.rb +94 -0
  90. data/test/controller/test_test.rb +194 -5
  91. data/test/controller/url_rewriter_test.rb +46 -0
  92. data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
  93. data/test/fixtures/layouts/yield.rhtml +2 -0
  94. data/test/fixtures/multipart/binary_file +0 -0
  95. data/test/fixtures/multipart/large_text_file +10 -0
  96. data/test/fixtures/multipart/mixed_files +0 -0
  97. data/test/fixtures/multipart/single_parameter +5 -0
  98. data/test/fixtures/multipart/text_file +10 -0
  99. data/test/fixtures/test/_customer_greeting.rhtml +1 -0
  100. data/test/fixtures/test/_hash_object.rhtml +1 -0
  101. data/test/fixtures/test/_person.rhtml +2 -0
  102. data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
  103. data/test/fixtures/test/content_for.rhtml +2 -0
  104. data/test/fixtures/test/potential_conflicts.rhtml +4 -0
  105. data/test/template/active_record_helper_test.rb +15 -8
  106. data/test/template/asset_tag_helper_test.rb +40 -16
  107. data/test/template/compiled_templates_tests.rb +63 -0
  108. data/test/template/date_helper_test.rb +80 -4
  109. data/test/template/form_helper_test.rb +48 -42
  110. data/test/template/form_options_helper_test.rb +40 -40
  111. data/test/template/form_tag_helper_test.rb +21 -15
  112. data/test/template/java_script_macros_helper_test.rb +56 -0
  113. data/test/template/javascript_helper_test.rb +70 -47
  114. data/test/template/number_helper_test.rb +2 -0
  115. data/test/template/tag_helper_test.rb +9 -0
  116. data/test/template/text_helper_test.rb +146 -1
  117. data/test/template/upload_progress_helper_testx.rb +11 -147
  118. data/test/template/url_helper_test.rb +90 -22
  119. data/test/testing_sandbox.rb +26 -0
  120. metadata +37 -7
  121. data/lib/action_controller/auto_complete.rb +0 -47
  122. data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
  123. data/lib/action_controller/session.rb +0 -14
@@ -0,0 +1,188 @@
1
+ require File.dirname(__FILE__) + '/tag_helper'
2
+
3
+ module ActionView
4
+ module Helpers
5
+ # Provides a set of helpers for creating JavaScript macros that rely on and often bundle methods from JavaScriptHelper into
6
+ # larger units. These macros also rely on counterparts in the controller that provide them with their backing. The in-place
7
+ # editing relies on ActionController::Base.in_place_edit_for and the autocompletion relies on
8
+ # ActionController::Base.auto_complete_for.
9
+ module JavaScriptMacrosHelper
10
+ # Makes an HTML element specified by the DOM ID +field_id+ become an in-place
11
+ # editor of a property.
12
+ #
13
+ # A form is automatically created and displayed when the user clicks the element,
14
+ # something like this:
15
+ # <form id="myElement-in-place-edit-form" target="specified url">
16
+ # <input name="value" text="The content of myElement"/>
17
+ # <input type="submit" value="ok"/>
18
+ # <a onclick="javascript to cancel the editing">cancel</a>
19
+ # </form>
20
+ #
21
+ # The form is serialized and sent to the server using an AJAX call, the action on
22
+ # the server should process the value and return the updated value in the body of
23
+ # the reponse. The element will automatically be updated with the changed value
24
+ # (as returned from the server).
25
+ #
26
+ # Required +options+ are:
27
+ # <tt>:url</tt>:: Specifies the url where the updated value should
28
+ # be sent after the user presses "ok".
29
+ #
30
+ # Addtional +options+ are:
31
+ # <tt>:rows</tt>:: Number of rows (more than 1 will use a TEXTAREA)
32
+ # <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
33
+ # <tt>:ok_text</tt>:: The text on the save link. (default: "ok")
34
+ # <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
35
+ # <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
36
+ # in the AJAX call, +form+ is an implicit parameter
37
+ def in_place_editor(field_id, options = {})
38
+ function = "new Ajax.InPlaceEditor("
39
+ function << "'#{field_id}', "
40
+ function << "'#{url_for(options[:url])}'"
41
+
42
+ js_options = {}
43
+ js_options['cancelText'] = options[:cancel_text] if options[:cancel_text]
44
+ js_options['okText'] = options[:save_text] if options[:save_text]
45
+ js_options['rows'] = options[:rows] if options[:rows]
46
+ js_options['ajaxOptions'] = options[:options] if options[:options]
47
+ js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with]
48
+ function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
49
+
50
+ function << ')'
51
+
52
+ javascript_tag(function)
53
+ end
54
+
55
+ # Renders the value of the specified object and method with in-place editing capabilities.
56
+ #
57
+ # See the RDoc on ActionController::InPlaceEditing to learn more about this.
58
+ def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {})
59
+ tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
60
+ tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
61
+ in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
62
+ tag.to_content_tag(tag_options[:tag], tag_options) +
63
+ in_place_editor(tag_options[:id], in_place_editor_options)
64
+ end
65
+
66
+ # Adds AJAX autocomplete functionality to the text input field with the
67
+ # DOM ID specified by +field_id+.
68
+ #
69
+ # This function expects that the called action returns a HTML <ul> list,
70
+ # or nothing if no entries should be displayed for autocompletion.
71
+ #
72
+ # You'll probably want to turn the browser's built-in autocompletion off,
73
+ # su be sure to include a autocomplete="off" attribute with your text
74
+ # input field.
75
+ #
76
+ # Required +options+ are:
77
+ # <tt>:url</tt>:: URL to call for autocompletion results
78
+ # in url_for format.
79
+ #
80
+ # Addtional +options+ are:
81
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
82
+ # innerHTML should be updated with the autocomplete
83
+ # entries returned by the AJAX request.
84
+ # Defaults to field_id + '_auto_complete'
85
+ # <tt>:with</tt>:: A JavaScript expression specifying the
86
+ # parameters for the XMLHttpRequest. This defaults
87
+ # to 'fieldname=value'.
88
+ # <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
89
+ # displayed while autocomplete is running.
90
+ # <tt>:tokens</tt>:: A string or an array of strings containing
91
+ # separator tokens for tokenized incremental
92
+ # autocompletion. Example: <tt>:tokens => ','</tt> would
93
+ # allow multiple autocompletion entries, separated
94
+ # by commas.
95
+ # <tt>:min_chars</tt>:: The minimum number of characters that should be
96
+ # in the input field before an Ajax call is made
97
+ # to the server.
98
+ # <tt>:on_hide</tt>:: A Javascript expression that is called when the
99
+ # autocompletion div is hidden. The expression
100
+ # should take two variables: element and update.
101
+ # Element is a DOM element for the field, update
102
+ # is a DOM element for the div from which the
103
+ # innerHTML is replaced.
104
+ # <tt>:on_show</tt>:: Like on_hide, only now the expression is called
105
+ # then the div is shown.
106
+ def auto_complete_field(field_id, options = {})
107
+ function = "new Ajax.Autocompleter("
108
+ function << "'#{field_id}', "
109
+ function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
110
+ function << "'#{url_for(options[:url])}'"
111
+
112
+ js_options = {}
113
+ js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens]
114
+ js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
115
+ js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
116
+ {:on_show => :onShow, :on_hide => :onHide, :min_chars => :min_chars}.each do |k,v|
117
+ js_options[v] = options[k] if options[k]
118
+ end
119
+ function << (', ' + options_for_javascript(js_options) + ')')
120
+
121
+ javascript_tag(function)
122
+ end
123
+
124
+ # Use this method in your view to generate a return for the AJAX autocomplete requests.
125
+ #
126
+ # Example action:
127
+ #
128
+ # def auto_complete_for_item_title
129
+ # @items = Item.find(:all,
130
+ # :conditions => [ 'LOWER(description) LIKE ?',
131
+ # '%' + request.raw_post.downcase + '%' ])
132
+ # render :inline => '<%= auto_complete_result(@items, 'description') %>'
133
+ # end
134
+ #
135
+ # The auto_complete_result can of course also be called from a view belonging to the
136
+ # auto_complete action if you need to decorate it further.
137
+ def auto_complete_result(entries, field, phrase = nil)
138
+ return unless entries
139
+ items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) }
140
+ content_tag("ul", items.uniq)
141
+ end
142
+
143
+ # Wrapper for text_field with added AJAX autocompletion functionality.
144
+ #
145
+ # In your controller, you'll need to define an action called
146
+ # auto_complete_for_object_method to respond the AJAX calls,
147
+ #
148
+ # See the RDoc on ActionController::AutoComplete to learn more about this.
149
+ def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})
150
+ (completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
151
+ text_field(object, method, tag_options) +
152
+ content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") +
153
+ auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options))
154
+ end
155
+
156
+ private
157
+ def auto_complete_stylesheet
158
+ content_tag("style", <<-EOT
159
+ div.auto_complete {
160
+ width: 350px;
161
+ background: #fff;
162
+ }
163
+ div.auto_complete ul {
164
+ border:1px solid #888;
165
+ margin:0;
166
+ padding:0;
167
+ width:100%;
168
+ list-style-type:none;
169
+ }
170
+ div.auto_complete ul li {
171
+ margin:0;
172
+ padding:3px;
173
+ }
174
+ div.auto_complete ul li.selected {
175
+ background-color: #ffb;
176
+ }
177
+ div.auto_complete ul strong.highlight {
178
+ color: #800;
179
+ margin:0;
180
+ padding:0;
181
+ }
182
+ EOT
183
+ )
184
+ end
185
+
186
+ end
187
+ end
188
+ end
@@ -7,17 +7,27 @@ module ActionView
7
7
  # actions in your controllers without reloading the page, but still update certain parts of it using injections into the
8
8
  # DOM. The common use case is having a form that adds a new element to a list without reloading the page.
9
9
  #
10
- # To be able to use the JavaScript helpers, you must either call <tt><%= define_javascript_functions %></tt> (which returns all
11
- # the JavaScript support functions in a <script> block) or reference the JavaScript library using
12
- # <tt><%= javascript_include_tag "prototype" %></tt> (which looks for the library in /javascripts/prototype.js). The latter is
13
- # recommended as the browser can then cache the library instead of fetching all the functions anew on every request.
10
+ # To be able to use the JavaScript helpers, you must include the Prototype JavaScript Framework and for some functions
11
+ # script.aculo.us (which both come with Rails) on your pages. Choose one of these options:
12
+ #
13
+ # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD section of your page (recommended):
14
+ # The function will return references to the JavaScript files created by the +rails+ command in your
15
+ # <tt>public/javascripts</tt> directory. Using it is recommended as the browser can then cache the libraries
16
+ # instead of fetching all the functions anew on every request.
17
+ # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but will only include the Prototype core library,
18
+ # which means you are able to use all basic AJAX functionality. For the script.aculo.us-based JavaScript helpers,
19
+ # like visual effects, autocompletion, drag and drop and so on, you should use the method described above.
20
+ # * Use <tt><%= define_javascript_functions %></tt>: this will copy all the JavaScript support functions within a single
21
+ # script block.
22
+ #
23
+ # For documentation on +javascript_include_tag+ see ActionView::Helpers::AssetTagHelper.
14
24
  #
15
25
  # If you're the visual type, there's an AJAX movie[http://www.rubyonrails.com/media/video/rails-ajax.mov] demonstrating
16
26
  # the use of form_remote_tag.
17
27
  module JavaScriptHelper
18
28
  unless const_defined? :CALLBACKS
19
29
  CALLBACKS =
20
- [:uninitialized, :loading, :loaded, :interactive, :complete, :failure].push((100..599).to_a).flatten
30
+ [:uninitialized, :loading, :loaded, :interactive, :complete, :failure, :success].push((100..599).to_a).flatten
21
31
  AJAX_OPTIONS = [ :before, :after, :condition, :url, :asynchronous, :method,
22
32
  :insertion, :position, :form, :with, :update, :script ].concat(CALLBACKS)
23
33
  JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
@@ -116,6 +126,11 @@ module ActionView
116
126
  # <tt>:before</tt>:: Called before request is initiated.
117
127
  # <tt>:after</tt>:: Called immediately after request was
118
128
  # initiated and before <tt>:loading</tt>.
129
+ # <tt>:submit</tt>:: Specifies the DOM element ID that's used
130
+ # as the parent of the form elements. By
131
+ # default this is the current form, but
132
+ # it could just as well be the ID of a
133
+ # table row or any other DOM element.
119
134
  def link_to_remote(name, options = {}, html_options = {})
120
135
  link_to_function(name, remote_function(options), html_options)
121
136
  end
@@ -126,7 +141,7 @@ module ActionView
126
141
  def periodically_call_remote(options = {})
127
142
  frequency = options[:frequency] || 10 # every ten seconds by default
128
143
  code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
129
- content_tag("script", code, options[:html_options] || {})
144
+ javascript_tag(code)
130
145
  end
131
146
 
132
147
  # Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular
@@ -137,6 +152,7 @@ module ActionView
137
152
  # A "fall-through" target for browsers that doesn't do JavaScript can be specified with the :action/:method options on :html
138
153
  #
139
154
  # form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") }
155
+ # The Hash passed to the :html key is equivalent to the options (2nd) argument in the FormTagHelper.form_tag method.
140
156
  #
141
157
  # By default the fall-through action is the same as the one specified in the :url (and the default method is :post).
142
158
  def form_remote_tag(options = {})
@@ -153,7 +169,7 @@ module ActionView
153
169
  # Returns a button input tag that will submit form using XMLHttpRequest in the background instead of regular
154
170
  # reloading POST arrangement. <tt>options</tt> argument is the same as in <tt>form_remote_tag</tt>
155
171
  def submit_to_remote(name, value, options = {})
156
- options[:with] = 'Form.serialize(this.form)'
172
+ options[:with] ||= 'Form.serialize(this.form)'
157
173
 
158
174
  options[:html] ||= {}
159
175
  options[:html][:type] = 'button'
@@ -235,7 +251,15 @@ module ActionView
235
251
  "eval(request.responseText)"
236
252
  end
237
253
 
238
- def remote_function(options) #:nodoc: for now
254
+ # Returns the javascript needed for a remote function.
255
+ # Takes the same arguments as link_to_remote.
256
+ #
257
+ # Example:
258
+ # <select id="options" onchange="<%= remote_function(:update => "options", :url => { :action => :update_options }) %>">
259
+ # <option value="0">Hello</option>
260
+ # <option value="1">World</option>
261
+ # </select>
262
+ def remote_function(options)
239
263
  javascript_options = options_for_ajax(options)
240
264
 
241
265
  update = ''
@@ -297,9 +321,10 @@ module ActionView
297
321
  #
298
322
  # Additional options are:
299
323
  # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
300
- # this field will be detected. Set this to a value
301
- # greater than zero to use time based observation
302
- # instead of event based observation.
324
+ # this field will be detected. Not setting this
325
+ # option at all or to a value equal to or less than
326
+ # zero will use event based observation instead of
327
+ # time based observation.
303
328
  # <tt>:update</tt>:: Specifies the DOM ID of the element whose
304
329
  # innerHTML should be updated with the
305
330
  # XMLHttpRequest response text.
@@ -309,9 +334,9 @@ module ActionView
309
334
  # refers to the new field value.
310
335
  #
311
336
  # Additionally, you may specify any of the options documented in
312
- # +link_to_remote.
337
+ # link_to_remote.
313
338
  def observe_field(field_id, options = {})
314
- if options[:frequency]
339
+ if options[:frequency] and options[:frequency] > 0
315
340
  build_observer('Form.Element.Observer', field_id, options)
316
341
  else
317
342
  build_observer('Form.Element.EventObserver', field_id, options)
@@ -330,86 +355,11 @@ module ActionView
330
355
  end
331
356
  end
332
357
 
333
-
334
- # Adds AJAX autocomplete functionality to the text input field with the
335
- # DOM ID specified by +field_id+.
336
- #
337
- # This function expects that the called action returns a HTML <ul> list,
338
- # or nothing if no entries should be displayed for autocompletion.
339
- #
340
- # You'll probably want to turn the browser's built-in autocompletion off,
341
- # su be sure to include a autocomplete="off" attribute with your text
342
- # input field.
343
- #
344
- # Required +options+ are:
345
- # <tt>:url</tt>:: Specifies the DOM ID of the element whose
346
- # innerHTML should be updated with the autocomplete
347
- # entries returned by XMLHttpRequest.
348
- #
349
- # Addtional +options+ are:
350
- # <tt>:update</tt>:: Specifies the DOM ID of the element whose
351
- # innerHTML should be updated with the autocomplete
352
- # entries returned by the AJAX request.
353
- # Defaults to field_id + '_auto_complete'
354
- # <tt>:with</tt>:: A JavaScript expression specifying the
355
- # parameters for the XMLHttpRequest. This defaults
356
- # to 'fieldname=value'.
357
- # <tt>:indicator</tt>:: Specifies the DOM ID of an elment which will be
358
- # displayed while autocomplete is running.
359
- def auto_complete_field(field_id, options = {})
360
- function = "new Ajax.Autocompleter("
361
- function << "'#{field_id}', "
362
- function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
363
- function << "'#{url_for(options[:url])}'"
364
-
365
- js_options = {}
366
- if options[:tokens] and options[:tokens].kind_of?(Array)
367
- js_options[:tokens] = "['#{options[:tokens].join('\',\'')}']"
368
- elsif options[:tokens]
369
- js_options[:tokens] = "'#{options[:tokens]}'" if options[:tokens]
370
- end
371
- js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
372
- js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
373
- function << (', ' + options_for_javascript(js_options) + ')')
374
-
375
- javascript_tag(function)
376
- end
377
-
378
- # Use this method in your view to generate a return for the AJAX automplete requests.
379
- #
380
- # Example action:
381
- #
382
- # def auto_complete_for_item_title
383
- # @items = Item.find(:all,
384
- # :conditions => [ 'LOWER(description) LIKE ?',
385
- # '%' + request.raw_post.downcase + '%' ])
386
- # render :inline => '<%= auto_complete_result(@items, 'description') %>'
387
- # end
388
- #
389
- # The auto_complete_result can of course also be called from a view belonging to the
390
- # auto_complete action if you need to decorate it further.
391
- def auto_complete_result(entries, field, phrase = nil)
392
- return unless entries
393
- items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) }
394
- content_tag("ul", items)
395
- end
396
-
397
- # Wrapper for text_field with added AJAX autocompletion functionality.
398
- #
399
- # In your controller, you'll need to define an action called
400
- # auto_complete_for_object_method to respond the AJAX calls,
401
- #
402
- # See the RDoc on ActionController::AutoComplete to learn more about this.
403
- def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})
404
- (completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
405
- text_field(object, method, { :autocomplete => "off" }.merge!(tag_options)) +
406
- content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") +
407
- auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options))
408
- end
409
-
410
358
  # Returns a JavaScript snippet to be used on the AJAX callbacks for starting
411
359
  # visual effects.
412
360
  #
361
+ # This method requires the inclusion of the script.aculo.us JavaScript library.
362
+ #
413
363
  # Example:
414
364
  # <%= link_to_remote "Reload", :update => "posts",
415
365
  # :url => { :action => "reload" },
@@ -435,6 +385,8 @@ module ActionView
435
385
  # changed. By default, the action called gets the serialized sortable
436
386
  # element as parameters.
437
387
  #
388
+ # This method requires the inclusion of the script.aculo.us JavaScript library.
389
+ #
438
390
  # Example:
439
391
  # <%= sortable_element("my_list", :url => { :action => "order" }) %>
440
392
  #
@@ -453,17 +405,16 @@ module ActionView
453
405
  options[option] = "'#{options[option]}'" if options[option]
454
406
  end
455
407
 
456
- if options[:containment] and options[:containment].kind_of?(Array)
457
- options[:containment] = "['#{options[:containment].join('\',\'')}']"
458
- elsif options[:containment]
459
- options[:containment] = "'#{options[:containment]}'" if options[:containment]
460
- end
408
+ options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
409
+ options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
461
410
 
462
411
  javascript_tag("Sortable.create('#{element_id}', #{options_for_javascript(options)})")
463
412
  end
464
413
 
465
414
  # Makes the element with the DOM ID specified by +element_id+ draggable.
466
415
  #
416
+ # This method requires the inclusion of the script.aculo.us JavaScript library.
417
+ #
467
418
  # Example:
468
419
  # <%= draggable_element("my_image", :revert => true)
469
420
  #
@@ -478,6 +429,8 @@ module ActionView
478
429
  # and make an AJAX call By default, the action called gets the DOM ID of the
479
430
  # element as parameter.
480
431
  #
432
+ # This method requires the inclusion of the script.aculo.us JavaScript library.
433
+ #
481
434
  # Example:
482
435
  # <%= drop_receiving_element("my_cart", :url => { :controller => "cart", :action => "add" }) %>
483
436
  #
@@ -488,12 +441,7 @@ module ActionView
488
441
  options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
489
442
  options.delete_if { |key, value| AJAX_OPTIONS.include?(key) }
490
443
 
491
- if options[:accept] and options[:accept].kind_of?(Array)
492
- options[:accept] = "['#{options[:accept].join('\',\'')}']"
493
- elsif options[:accept]
494
- options[:accept] = "'#{options[:accept]}'" if options[:accept]
495
- end
496
-
444
+ options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
497
445
  options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
498
446
 
499
447
  javascript_tag("Droppables.add('#{element_id}', #{options_for_javascript(options)})")
@@ -507,14 +455,27 @@ module ActionView
507
455
  # Returns a JavaScript tag with the +content+ inside. Example:
508
456
  # javascript_tag "alert('All is good')" # => <script type="text/javascript">alert('All is good')</script>
509
457
  def javascript_tag(content)
510
- content_tag("script", content, :type => "text/javascript")
458
+ content_tag("script", javascript_cdata_section(content), :type => "text/javascript")
511
459
  end
512
460
 
461
+ def javascript_cdata_section(content) #:nodoc:
462
+ "\n//#{cdata_section("\n#{content}\n//")}\n"
463
+ end
464
+
513
465
  private
514
466
  def options_for_javascript(options)
515
467
  '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
516
468
  end
517
469
 
470
+ def array_or_string_for_javascript(option)
471
+ js_option = if option.kind_of?(Array)
472
+ "['#{option.join('\',\'')}']"
473
+ elsif !option.nil?
474
+ "'#{option}'"
475
+ end
476
+ js_option
477
+ end
478
+
518
479
  def options_for_ajax(options)
519
480
  js_options = build_callbacks(options)
520
481
 
@@ -525,6 +486,8 @@ module ActionView
525
486
 
526
487
  if options[:form]
527
488
  js_options['parameters'] = 'Form.serialize(this)'
489
+ elsif options[:submit]
490
+ js_options['parameters'] = "Form.serialize(document.getElementById('#{options[:submit]}'))"
528
491
  elsif options[:with]
529
492
  js_options['parameters'] = options[:with]
530
493
  end
@@ -539,11 +502,11 @@ module ActionView
539
502
  def build_observer(klass, name, options = {})
540
503
  options[:with] ||= 'value' if options[:update]
541
504
  callback = remote_function(options)
542
- javascript = '<script type="text/javascript">'
543
- javascript << "new #{klass}('#{name}', "
505
+ javascript = "new #{klass}('#{name}', "
544
506
  javascript << "#{options[:frequency]}, " if options[:frequency]
545
507
  javascript << "function(element, value) {"
546
- javascript << "#{callback}})</script>"
508
+ javascript << "#{callback}})"
509
+ javascript_tag(javascript)
547
510
  end
548
511
 
549
512
  def build_callbacks(options)
@@ -557,34 +520,6 @@ module ActionView
557
520
  callbacks
558
521
  end
559
522
 
560
- def auto_complete_stylesheet
561
- content_tag("style", <<-EOT
562
- div.auto_complete {
563
- width: 350px;
564
- background: #fff;
565
- }
566
- div.auto_complete ul {
567
- border:1px solid #888;
568
- margin:0;
569
- padding:0;
570
- width:100%;
571
- list-style-type:none;
572
- }
573
- div.auto_complete ul li {
574
- margin:0;
575
- padding:3px;
576
- }
577
- div.auto_complete ul li.selected {
578
- background-color: #ffb;
579
- }
580
- div.auto_complete ul strong.highlight {
581
- color: #800;
582
- margin:0;
583
- padding:0;
584
- }
585
- EOT
586
- )
587
- end
588
523
  end
589
524
 
590
525
  JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper