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.
- data/CHANGELOG +237 -0
- data/README +12 -12
- data/lib/action_controller.rb +17 -12
- data/lib/action_controller/assertions.rb +119 -67
- data/lib/action_controller/base.rb +184 -102
- data/lib/action_controller/benchmarking.rb +35 -6
- data/lib/action_controller/caching.rb +115 -58
- data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
- data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
- data/lib/action_controller/cgi_process.rb +23 -20
- data/lib/action_controller/components.rb +11 -2
- data/lib/action_controller/dependencies.rb +0 -5
- data/lib/action_controller/deprecated_redirects.rb +17 -0
- data/lib/action_controller/filters.rb +13 -9
- data/lib/action_controller/flash.rb +7 -7
- data/lib/action_controller/helpers.rb +1 -14
- data/lib/action_controller/layout.rb +40 -29
- data/lib/action_controller/macros/auto_complete.rb +52 -0
- data/lib/action_controller/macros/in_place_editing.rb +32 -0
- data/lib/action_controller/pagination.rb +44 -28
- data/lib/action_controller/request.rb +54 -40
- data/lib/action_controller/rescue.rb +8 -6
- data/lib/action_controller/routing.rb +77 -28
- data/lib/action_controller/scaffolding.rb +10 -14
- data/lib/action_controller/session/active_record_store.rb +36 -7
- data/lib/action_controller/session_management.rb +126 -0
- data/lib/action_controller/streaming.rb +14 -5
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
- data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
- data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
- data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
- data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
- data/lib/action_controller/test_process.rb +35 -17
- data/lib/action_controller/upload_progress.rb +52 -0
- data/lib/action_controller/url_rewriter.rb +21 -16
- data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
- data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +1 -1
- data/lib/action_view/base.rb +204 -60
- data/lib/action_view/compiled_templates.rb +70 -0
- data/lib/action_view/helpers/active_record_helper.rb +7 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
- data/lib/action_view/helpers/capture_helper.rb +2 -10
- data/lib/action_view/helpers/date_helper.rb +21 -13
- data/lib/action_view/helpers/form_helper.rb +14 -10
- data/lib/action_view/helpers/form_options_helper.rb +4 -4
- data/lib/action_view/helpers/form_tag_helper.rb +59 -25
- data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
- data/lib/action_view/helpers/javascript_helper.rb +68 -133
- data/lib/action_view/helpers/javascripts/controls.js +427 -165
- data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
- data/lib/action_view/helpers/javascripts/effects.js +766 -277
- data/lib/action_view/helpers/javascripts/prototype.js +906 -218
- data/lib/action_view/helpers/javascripts/slider.js +258 -0
- data/lib/action_view/helpers/number_helper.rb +4 -3
- data/lib/action_view/helpers/pagination_helper.rb +42 -27
- data/lib/action_view/helpers/tag_helper.rb +25 -11
- data/lib/action_view/helpers/text_helper.rb +119 -13
- data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
- data/lib/action_view/helpers/url_helper.rb +68 -21
- data/lib/action_view/partials.rb +17 -6
- data/lib/action_view/template_error.rb +19 -24
- data/rakefile +4 -3
- data/test/abstract_unit.rb +2 -1
- data/test/controller/action_pack_assertions_test.rb +62 -2
- data/test/controller/active_record_assertions_test.rb +5 -6
- data/test/controller/active_record_store_test.rb +23 -1
- data/test/controller/addresses_render_test.rb +4 -0
- data/test/controller/{base_tests.rb → base_test.rb} +4 -3
- data/test/controller/benchmark_test.rb +36 -0
- data/test/controller/caching_filestore.rb +22 -40
- data/test/controller/capture_test.rb +10 -1
- data/test/controller/cgi_test.rb +145 -23
- data/test/controller/components_test.rb +50 -0
- data/test/controller/custom_handler_test.rb +3 -3
- data/test/controller/fake_controllers.rb +24 -0
- data/test/controller/filters_test.rb +6 -6
- data/test/controller/flash_test.rb +6 -6
- data/test/controller/fragment_store_setting_test.rb +45 -0
- data/test/controller/helper_test.rb +1 -3
- data/test/controller/new_render_test.rb +119 -7
- data/test/controller/redirect_test.rb +11 -1
- data/test/controller/render_test.rb +34 -1
- data/test/controller/request_test.rb +14 -5
- data/test/controller/routing_test.rb +238 -42
- data/test/controller/send_file_test.rb +11 -10
- data/test/controller/session_management_test.rb +94 -0
- data/test/controller/test_test.rb +194 -5
- data/test/controller/url_rewriter_test.rb +46 -0
- data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
- data/test/fixtures/layouts/yield.rhtml +2 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/multipart/large_text_file +10 -0
- data/test/fixtures/multipart/mixed_files +0 -0
- data/test/fixtures/multipart/single_parameter +5 -0
- data/test/fixtures/multipart/text_file +10 -0
- data/test/fixtures/test/_customer_greeting.rhtml +1 -0
- data/test/fixtures/test/_hash_object.rhtml +1 -0
- data/test/fixtures/test/_person.rhtml +2 -0
- data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
- data/test/fixtures/test/content_for.rhtml +2 -0
- data/test/fixtures/test/potential_conflicts.rhtml +4 -0
- data/test/template/active_record_helper_test.rb +15 -8
- data/test/template/asset_tag_helper_test.rb +40 -16
- data/test/template/compiled_templates_tests.rb +63 -0
- data/test/template/date_helper_test.rb +80 -4
- data/test/template/form_helper_test.rb +48 -42
- data/test/template/form_options_helper_test.rb +40 -40
- data/test/template/form_tag_helper_test.rb +21 -15
- data/test/template/java_script_macros_helper_test.rb +56 -0
- data/test/template/javascript_helper_test.rb +70 -47
- data/test/template/number_helper_test.rb +2 -0
- data/test/template/tag_helper_test.rb +9 -0
- data/test/template/text_helper_test.rb +146 -1
- data/test/template/upload_progress_helper_testx.rb +11 -147
- data/test/template/url_helper_test.rb +90 -22
- data/test/testing_sandbox.rb +26 -0
- metadata +37 -7
- data/lib/action_controller/auto_complete.rb +0 -47
- data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
- 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
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
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
|
-
|
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]
|
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
|
-
|
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.
|
301
|
-
#
|
302
|
-
#
|
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
|
-
#
|
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
|
-
|
457
|
-
|
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
|
-
|
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
|
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}})
|
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
|