bivouac 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README +9 -0
- data/bin/bivouac +15 -19
- data/doc/rdoc/classes/BivouacHelpers.html +117 -0
- data/doc/rdoc/classes/BivouacHelpers/BaseView.html +140 -0
- data/doc/rdoc/classes/BivouacHelpers/FormView.html +398 -0
- data/doc/rdoc/classes/BivouacHelpers/HtmlView.html +274 -0
- data/doc/rdoc/classes/BivouacHelpers/JavaScriptView.html +573 -0
- data/doc/rdoc/classes/BivouacHelpers/ScriptAculoUsView.html +258 -0
- data/doc/rdoc/classes/BivouacHelpers/TooltipView.html +158 -0
- data/doc/rdoc/classes/JavaScriptGenerator.html +564 -0
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/AUTHORS.html +14 -0
- data/doc/rdoc/files/COPYING.html +14 -0
- data/doc/rdoc/files/README.html +40 -1
- data/doc/rdoc/files/lib/bivouac/helpers/view/goh/base_rb.html +109 -0
- data/doc/rdoc/files/lib/bivouac/helpers/view/goh/form_rb.html +109 -0
- data/doc/rdoc/files/lib/bivouac/helpers/view/goh/html_rb.html +109 -0
- data/doc/rdoc/files/lib/bivouac/helpers/view/goh/javascript_rb.html +113 -0
- data/doc/rdoc/files/lib/bivouac/helpers/view/goh/scriptaculous_rb.html +113 -0
- data/doc/rdoc/files/lib/bivouac/helpers/view/goh/tooltip_rb.html +109 -0
- data/examples/bivouac_sample/app/bivouac_sample.rb +61 -0
- data/examples/bivouac_sample/app/controllers/autocomplete.rb +24 -0
- data/examples/bivouac_sample/app/controllers/drag_and_drop.rb +11 -0
- data/examples/bivouac_sample/app/controllers/index.rb +7 -0
- data/examples/bivouac_sample/app/controllers/observe.rb +14 -0
- data/examples/bivouac_sample/app/controllers/periodically_call.rb +12 -0
- data/examples/bivouac_sample/app/controllers/remote_form.rb +10 -0
- data/examples/bivouac_sample/app/controllers/remote_link.rb +11 -0
- data/examples/bivouac_sample/app/controllers/submit_remote.rb +17 -0
- data/examples/bivouac_sample/app/controllers/toggle.rb +10 -0
- data/examples/bivouac_sample/app/controllers/toggle_sortable.rb +11 -0
- data/examples/bivouac_sample/app/helpers/_helpers.rb +25 -0
- data/examples/bivouac_sample/app/helpers/source.rb +25 -0
- data/examples/bivouac_sample/app/views/_autocomplete_result.rb +9 -0
- data/examples/bivouac_sample/app/views/_drag_and_drop_result.rb +5 -0
- data/examples/bivouac_sample/app/views/_observe_result.rb +17 -0
- data/examples/bivouac_sample/app/views/_periodically_call_result.rb +5 -0
- data/examples/bivouac_sample/app/views/_remote_link_result.rb +5 -0
- data/examples/bivouac_sample/app/views/_sortable_result.rb +5 -0
- data/examples/bivouac_sample/app/views/_submit_remote_result.rb +5 -0
- data/examples/bivouac_sample/app/views/autocomplete.rb +28 -0
- data/examples/bivouac_sample/app/views/drag_and_drop.rb +35 -0
- data/examples/bivouac_sample/app/views/index.rb +19 -0
- data/examples/bivouac_sample/app/views/observe.rb +19 -0
- data/examples/bivouac_sample/app/views/periodically_call.rb +17 -0
- data/examples/bivouac_sample/app/views/remote_form.rb +19 -0
- data/examples/bivouac_sample/app/views/remote_link.rb +20 -0
- data/examples/bivouac_sample/app/views/submit_remote.rb +21 -0
- data/examples/bivouac_sample/app/views/toggle.rb +44 -0
- data/examples/bivouac_sample/app/views/toggle_sortable.rb +52 -0
- data/examples/bivouac_sample/config/environment.rb +37 -0
- data/examples/bivouac_sample/config/postamble.rb +62 -0
- data/examples/bivouac_sample/log/BivouacSample.log +77 -0
- data/examples/bivouac_sample/public/images/camping.png +0 -0
- data/examples/bivouac_sample/public/index.html +242 -0
- data/examples/bivouac_sample/public/javascripts/builder.js +131 -0
- data/examples/bivouac_sample/public/javascripts/controls.js +835 -0
- data/examples/bivouac_sample/public/javascripts/dragdrop.js +944 -0
- data/examples/bivouac_sample/public/javascripts/effects.js +1090 -0
- data/examples/bivouac_sample/public/javascripts/prototype.js +2515 -0
- data/examples/bivouac_sample/public/javascripts/scriptaculous.js +51 -0
- data/examples/bivouac_sample/public/javascripts/slider.js +278 -0
- data/examples/bivouac_sample/public/javascripts/tooltip.js +208 -0
- data/examples/bivouac_sample/public/javascripts/unittest.js +564 -0
- data/examples/bivouac_sample/public/stylesheets/autocomplete.css +22 -0
- data/examples/bivouac_sample/public/stylesheets/coderay.css +104 -0
- data/examples/bivouac_sample/script/generate +3 -0
- data/examples/bivouac_sample/script/server +5 -0
- data/lib/bivouac/helpers/view/goh/base.rb +16 -0
- data/lib/bivouac/helpers/view/goh/form.rb +170 -0
- data/lib/bivouac/helpers/view/goh/html.rb +138 -0
- data/lib/bivouac/helpers/view/goh/javascript.rb +532 -0
- data/lib/bivouac/helpers/view/goh/scriptaculous.rb +133 -0
- data/lib/bivouac/helpers/view/goh/tooltip.rb +33 -0
- data/lib/bivouac/template.rb +3 -3
- data/lib/bivouac/template/application/helpers_erb.rb +11 -0
- data/lib/bivouac/template/application/helpers_goh.rb +25 -0
- data/lib/bivouac/template/application/postamble.rb +62 -0
- data/lib/bivouac/template/application_erb.rb +4 -0
- data/lib/bivouac/template/application_goh.rb +4 -0
- data/lib/bivouac/template/environment.rb +19 -5
- data/lib/bivouac/template/server.rb +1 -1
- data/lib/bivouac/template/static/autocomplete.css +22 -0
- data/lib/bivouac/template/static/builder.js +131 -0
- data/lib/bivouac/template/static/controls.js +835 -0
- data/lib/bivouac/template/static/dragdrop.js +944 -0
- data/lib/bivouac/template/static/effects.js +1090 -0
- data/lib/bivouac/template/static/prototype.js +2515 -0
- data/lib/bivouac/template/static/scriptaculous.js +51 -0
- data/lib/bivouac/template/static/slider.js +278 -0
- data/lib/bivouac/template/static/tooltip.js +208 -0
- data/lib/bivouac/template/static/unittest.js +564 -0
- metadata +124 -7
- data/lib/bivouac/template/application/postamble_cgi.rb +0 -8
- data/lib/bivouac/template/application/postamble_fastcgi.rb +0 -8
- data/lib/bivouac/template/application/postamble_mongrel.rb +0 -19
- data/lib/bivouac/template/application/postamble_none.rb +0 -1
- data/lib/bivouac/template/application/postamble_webrick.rb +0 -19
@@ -0,0 +1,532 @@
|
|
1
|
+
# bivouac/helpers/view/javascript
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
class JavaScriptGenerator
|
5
|
+
def initialize( context, &block ) #:nodoc:
|
6
|
+
@context = context
|
7
|
+
@source = ""
|
8
|
+
yield( self )
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s #:nodoc:
|
12
|
+
javascript = "try {\n#{@source}\n} catch (e) "
|
13
|
+
javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{@source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
|
14
|
+
|
15
|
+
return javascript
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a element reference by finding it through +id+ in the DOM. This element can then be
|
19
|
+
# used for further method calls. Examples:
|
20
|
+
#
|
21
|
+
# page['blank_slate'] # => $('blank_slate');
|
22
|
+
# page['blank_slate'].show # => $('blank_slate').show();
|
23
|
+
# page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
|
24
|
+
def []( id )
|
25
|
+
# JavaScriptElementProxy.new(self, id)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
|
29
|
+
# used for further method calls. Examples:
|
30
|
+
#
|
31
|
+
# page.select('p') # => $$('p');
|
32
|
+
# page.select('p.welcome b').first # => $$('p.welcome b').first();
|
33
|
+
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
|
34
|
+
#
|
35
|
+
# You can also use prototype enumerations with the collection. Observe:
|
36
|
+
#
|
37
|
+
# page.select('#items li').each do |value|
|
38
|
+
# value.hide
|
39
|
+
# end
|
40
|
+
# # => $$('#items li').each(function(value) { value.hide(); });
|
41
|
+
#
|
42
|
+
# Though you can call the block param anything you want, they are always rendered in the
|
43
|
+
# javascript as 'value, index.' Other enumerations, like collect() return the last statement:
|
44
|
+
#
|
45
|
+
# page.select('#items li').collect('hidden') do |item|
|
46
|
+
# item.hide
|
47
|
+
# end
|
48
|
+
# # => var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
|
49
|
+
def select(pattern)
|
50
|
+
# JavaScriptElementCollectionProxy.new(self, pattern)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Writes raw JavaScript to the page.
|
54
|
+
def <<(javascript)
|
55
|
+
@source << javascript
|
56
|
+
end
|
57
|
+
|
58
|
+
# Displays an alert dialog with the given +message+.
|
59
|
+
def alert(message)
|
60
|
+
call( "alert", message )
|
61
|
+
end
|
62
|
+
|
63
|
+
# Assigns the JavaScript +variable+ the given +value+.
|
64
|
+
def assign( variable, value )
|
65
|
+
record "#{variable} = #{value.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates a script.aculo.us draggable element.
|
69
|
+
# See ActionView::Helpers::ScriptaculousHelper for more information.
|
70
|
+
def draggable( id, options = {} )
|
71
|
+
record @context.draggable_element_js( id, options ) + ";\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Creates a script.aculo.us drop receiving element.
|
75
|
+
# See ActionView::Helpers::ScriptaculousHelper for more information.
|
76
|
+
def drop_receiving( id, options = {} )
|
77
|
+
record @context.drop_receiving_element_js( id, options ) + ";\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Hides the visible DOM elements with the given +ids+.
|
81
|
+
def hide( *ids )
|
82
|
+
loop_on_multiple_args 'Element.hide', ids
|
83
|
+
end
|
84
|
+
|
85
|
+
# Inserts HTML at the specified +position+ relative to the DOM element
|
86
|
+
# identified by the given +id+.
|
87
|
+
#
|
88
|
+
# +position+ may be one of:
|
89
|
+
#
|
90
|
+
# <tt>:top</tt>:: HTML is inserted inside the element, before the
|
91
|
+
# element's existing content.
|
92
|
+
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
|
93
|
+
# element's existing content.
|
94
|
+
# <tt>:before</tt>:: HTML is inserted immediately preceeding the element.
|
95
|
+
# <tt>:after</tt>:: HTML is inserted immediately following the element.
|
96
|
+
#
|
97
|
+
# +data+ may be a string of HTML to insert.
|
98
|
+
def insert_html(position, id, data)
|
99
|
+
insertion = position.to_s.camelize
|
100
|
+
call "new Insertion.#{insertion}", id, data
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calls the JavaScript +function+, optionally with the given +arguments+.
|
104
|
+
def call(function, *arguments)
|
105
|
+
record "#{function}(#{arguments_for_call(arguments)});\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Redirects the browser to the given +location+.
|
109
|
+
def redirect_to(location)
|
110
|
+
assign 'window.location.href', location
|
111
|
+
end
|
112
|
+
|
113
|
+
# Removes the DOM elements with the given ids from the page.
|
114
|
+
def remove(*ids)
|
115
|
+
loop_on_multiple_args 'Element.remove', ids
|
116
|
+
end
|
117
|
+
|
118
|
+
# Replaces the inner HTML of the DOM element with the given +id+.
|
119
|
+
#
|
120
|
+
# +data+ may be a string of HTML to insert
|
121
|
+
#
|
122
|
+
# # Replace the HTML of the DOM element having ID 'person-45' with the
|
123
|
+
# # 'person' partial for the appropriate object.
|
124
|
+
# replace_html 'person-45', render( 'person', :object => @person )
|
125
|
+
#
|
126
|
+
def replace_html(id, data)
|
127
|
+
call 'Element.update', id, data
|
128
|
+
end
|
129
|
+
|
130
|
+
# Replaces the "outer HTML" (i.e., the entire element, not just its
|
131
|
+
# contents) of the DOM element with the given +id+.
|
132
|
+
#
|
133
|
+
# +data+ may be a string of HTML to insert.
|
134
|
+
# For example:
|
135
|
+
#
|
136
|
+
# # Replace the DOM element having ID 'person-45' with the
|
137
|
+
# # 'person' partial for the appropriate object.
|
138
|
+
# replace 'person-45', render( 'person', :object => @person )
|
139
|
+
def replace(id, data)
|
140
|
+
call 'Element.replace', id, data
|
141
|
+
end
|
142
|
+
|
143
|
+
# Shows hidden DOM elements with the given +ids+.
|
144
|
+
def show(*ids)
|
145
|
+
loop_on_multiple_args 'Element.show', ids
|
146
|
+
end
|
147
|
+
|
148
|
+
# Creates a script.aculo.us sortable element. Useful to recreate
|
149
|
+
# sortable elements after items get added or deleted.
|
150
|
+
def sortable(id, options = {})
|
151
|
+
record @context.sortable_element_js( id, options ) + ";\n"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Creates a script.aculo.us unsortable element. Useful to recreate
|
155
|
+
# unsortable elements after items get added or deleted.
|
156
|
+
def unsortable( id )
|
157
|
+
record @context.unsortable_element_js( id ) + ";\n"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Toggles the visibility of the DOM elements with the given +ids+.
|
161
|
+
def toggle(*ids)
|
162
|
+
loop_on_multiple_args 'Element.toggle', ids
|
163
|
+
end
|
164
|
+
|
165
|
+
# Starts a http://script.aculo.us visual effect.
|
166
|
+
def visual_effect(name, element_id = false, js_options = {})
|
167
|
+
record( @context.visual_effect( name, element_id, js_options ) + ";\n" )
|
168
|
+
end
|
169
|
+
|
170
|
+
# Executes the content of the block after a delay of +seconds+. Example:
|
171
|
+
#
|
172
|
+
# page.delay(20) do
|
173
|
+
# page.visual_effect :fade, 'notice'
|
174
|
+
# end
|
175
|
+
def delay(seconds = 1)
|
176
|
+
record "setTimeout(function() {\n\n"
|
177
|
+
yield
|
178
|
+
record "}, #{(seconds * 1000).to_i})"
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
def record( line )
|
183
|
+
self << line
|
184
|
+
end
|
185
|
+
|
186
|
+
def arguments_for_call(arguments)
|
187
|
+
arguments.map { |argument| argument.inspect }.join ', '
|
188
|
+
end
|
189
|
+
|
190
|
+
def loop_on_multiple_args(method, ids)
|
191
|
+
record( ids.size>1 ?
|
192
|
+
"#{ids.inspect}.each(#{method});\n" :
|
193
|
+
"#{method}(#{ids.first.inspect});\n" )
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
module BivouacHelpers
|
198
|
+
module JavaScriptView
|
199
|
+
CALLBACKS = [ :uninitialized, :loading, :loaded, :interactive, :complete, :failure, :success ] + ('100'..'599').to_a
|
200
|
+
|
201
|
+
# Escape carrier returns and single and double quotes for JavaScript segments.
|
202
|
+
def escape_javascript(javascript)
|
203
|
+
(javascript || '').gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns a JavaScript tag with the +block+ inside.
|
207
|
+
# Example:
|
208
|
+
#
|
209
|
+
# javascript_tag( "alert('Hello World!')", :defer => 'true' )
|
210
|
+
#
|
211
|
+
# Returns:
|
212
|
+
#
|
213
|
+
# <script defer="true" type="text/javascript">
|
214
|
+
# //<![CDATA[
|
215
|
+
# alert('Hello World!')
|
216
|
+
# //]]>
|
217
|
+
# </script>
|
218
|
+
def javascript_tag( content, options = {} )
|
219
|
+
options[:type] = "text/javascript"
|
220
|
+
script( options ) do
|
221
|
+
"//<![CDATA[\n" + content + "\n//]]>"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Yields a JavaScriptGenerator and returns the generated JavaScript code.
|
226
|
+
# Use this to update multiple elements on a page in an Ajax response. See
|
227
|
+
# JavaScriptGenerator for more information.
|
228
|
+
def update_page( &block )
|
229
|
+
JavaScriptGenerator.new( self, &block ).to_s
|
230
|
+
end
|
231
|
+
|
232
|
+
# Works like update_page but wraps the generated JavaScript in a <script>
|
233
|
+
# tag. See JavaScriptGenerator for more information.
|
234
|
+
def update_page_tag( options = {}, &block )
|
235
|
+
javascript_tag update_page( &block ), options
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns a link that will trigger a JavaScript function using the onclick
|
239
|
+
# handler and return false after the fact.
|
240
|
+
#
|
241
|
+
# The function argument can be omitted in favor of an update_page block,
|
242
|
+
# which evaluates to a string when the template is rendered (instead of
|
243
|
+
# making an Ajax request first).
|
244
|
+
#
|
245
|
+
# Examples:
|
246
|
+
#
|
247
|
+
# link_to_function "Greeting", "alert('Hello world!')"
|
248
|
+
# Produces:
|
249
|
+
# <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
|
250
|
+
#
|
251
|
+
# link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
|
252
|
+
# Produces:
|
253
|
+
# <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
|
254
|
+
# <img src="/images/delete.png?" alt="Delete"/>
|
255
|
+
# </a>
|
256
|
+
#
|
257
|
+
# link_to_function("Show me more", nil, :id => "more_link") do |page|
|
258
|
+
# page[:details].visual_effect :toggle_blind
|
259
|
+
# page[:more_link].replace_html "Show me less"
|
260
|
+
# end
|
261
|
+
# Produces:
|
262
|
+
# <a href="#" id="more_link" onclick="try {
|
263
|
+
# $("details").visualEffect("toggle_blind");
|
264
|
+
# $("more_link").update("Show me less");
|
265
|
+
# }
|
266
|
+
# catch (e) {
|
267
|
+
# alert('RJS error:\n\n' + e.toString());
|
268
|
+
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
269
|
+
# \n$(\"more_link\").update(\"Show me less\");');
|
270
|
+
# throw e
|
271
|
+
# };
|
272
|
+
# return false;">Show me more</a>
|
273
|
+
def link_to_function( name, *args, &block )
|
274
|
+
html_options = args.last.is_a?(Hash) ? args.pop : {}
|
275
|
+
function = args[0] || ''
|
276
|
+
|
277
|
+
function = update_page( &block ) if block_given?
|
278
|
+
|
279
|
+
a( html_options.merge({
|
280
|
+
:href => html_options[:href] || "#",
|
281
|
+
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
|
282
|
+
}) ) do
|
283
|
+
name
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Returns a link to a remote action defined by <tt>options[:url]</tt>
|
288
|
+
# that's called in the background using XMLHttpRequest. The result of
|
289
|
+
# that request can then be inserted into a DOM object whose id can be
|
290
|
+
# specified with <tt>options[:update]</tt>. Usually, the result would
|
291
|
+
# be a partial prepared by the controller.
|
292
|
+
#
|
293
|
+
# Examples:
|
294
|
+
# link_to_remote "Delete this post", :update => "posts",
|
295
|
+
# :url => R(Destroy, 1)
|
296
|
+
# link_to_remote(image_tag("refresh"), :update => "emails",
|
297
|
+
# :url => R(ListEmails)
|
298
|
+
#
|
299
|
+
# You can also specify a hash for <tt>options[:update]</tt> to allow for
|
300
|
+
# easy redirection of output to an other DOM element if a server-side
|
301
|
+
# error occurs:
|
302
|
+
#
|
303
|
+
# Example:
|
304
|
+
# link_to_remote "Delete this post",
|
305
|
+
# :url => R(Destroy, 1)
|
306
|
+
# :update => { :success => "posts", :failure => "error" }
|
307
|
+
#
|
308
|
+
# Optionally, you can use the <tt>options[:position]</tt> parameter to
|
309
|
+
# influence how the target DOM element is updated. It must be one of
|
310
|
+
# <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
|
311
|
+
#
|
312
|
+
# To access the server response, use <tt>request.responseText</tt>, to
|
313
|
+
# find out the HTTP status, use <tt>request.status</tt>.
|
314
|
+
#
|
315
|
+
# Example:
|
316
|
+
# link_to_remote word,
|
317
|
+
# :url => R(Undo, word_counter)
|
318
|
+
# :complete => "undoRequestCompleted(request)"
|
319
|
+
#
|
320
|
+
# The callbacks that may be specified are (in order):
|
321
|
+
#
|
322
|
+
# <tt>:loading</tt>:: Called when the remote document is being
|
323
|
+
# loaded with data by the browser.
|
324
|
+
# <tt>:loaded</tt>:: Called when the browser has finished loading
|
325
|
+
# the remote document.
|
326
|
+
# <tt>:interactive</tt>:: Called when the user can interact with the
|
327
|
+
# remote document, even though it has not
|
328
|
+
# finished loading.
|
329
|
+
# <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
|
330
|
+
# and the HTTP status code is in the 2XX range.
|
331
|
+
# <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
|
332
|
+
# and the HTTP status code is not in the 2XX
|
333
|
+
# range.
|
334
|
+
# <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
|
335
|
+
# (fires after success/failure if they are
|
336
|
+
# present).
|
337
|
+
#
|
338
|
+
# You can further refine <tt>:success</tt> and <tt>:failure</tt> by
|
339
|
+
# adding additional callbacks for specific status codes.
|
340
|
+
#
|
341
|
+
# Example:
|
342
|
+
# link_to_remote word,
|
343
|
+
# :url => R(Action),
|
344
|
+
# 404 => "alert('Not found...? Wrong URL...?')",
|
345
|
+
# :failure => "alert('HTTP Error ' + request.status + '!')"
|
346
|
+
#
|
347
|
+
# A status code callback overrides the success/failure handlers if
|
348
|
+
# present.
|
349
|
+
#
|
350
|
+
# If you for some reason or another need synchronous processing (that'll
|
351
|
+
# block the browser while the request is happening), you can specify
|
352
|
+
# <tt>options[:type] = :synchronous</tt>.
|
353
|
+
#
|
354
|
+
# You can customize further browser side call logic by passing in
|
355
|
+
# JavaScript code snippets via some optional parameters. In their order
|
356
|
+
# of use these are:
|
357
|
+
#
|
358
|
+
# <tt>:confirm</tt>:: Adds confirmation dialog.
|
359
|
+
# <tt>:condition</tt>:: Perform remote request conditionally
|
360
|
+
# by this expression. Use this to
|
361
|
+
# describe browser-side conditions when
|
362
|
+
# request should not be initiated.
|
363
|
+
# <tt>:before</tt>:: Called before request is initiated.
|
364
|
+
# <tt>:after</tt>:: Called immediately after request was
|
365
|
+
# initiated and before <tt>:loading</tt>.
|
366
|
+
# <tt>:submit</tt>:: Specifies the DOM element ID that's used
|
367
|
+
# as the parent of the form elements. By
|
368
|
+
# default this is the current form, but
|
369
|
+
# it could just as well be the ID of a
|
370
|
+
# table row or any other DOM element.
|
371
|
+
def link_to_remote(name, options = {}, html_options = {})
|
372
|
+
link_to_function(name, remote_function(options), html_options)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Periodically calls the specified url (<tt>options[:url]</tt>) every
|
376
|
+
# <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
|
377
|
+
# update a specified div (<tt>options[:update]</tt>) with the results
|
378
|
+
# of the remote call. The options for specifying the target with :url
|
379
|
+
# and defining callbacks is the same as link_to_remote.
|
380
|
+
def periodically_call_remote(options = {})
|
381
|
+
frequency = options[:frequency] || 10 # every ten seconds by default
|
382
|
+
code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
|
383
|
+
javascript_tag(code)
|
384
|
+
end
|
385
|
+
|
386
|
+
# Observes the field with the DOM ID specified by +field_id+ and makes
|
387
|
+
# an Ajax call when its contents have changed.
|
388
|
+
#
|
389
|
+
# Required +options+ are either of:
|
390
|
+
# <tt>:url</tt>:: +url_for+-style options for the action to call
|
391
|
+
# when the field has changed.
|
392
|
+
# <tt>:function</tt>:: Instead of making a remote call to a URL, you
|
393
|
+
# can specify a function to be called instead.
|
394
|
+
#
|
395
|
+
# Additional options are:
|
396
|
+
# <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
|
397
|
+
# this field will be detected. Not setting this
|
398
|
+
# option at all or to a value equal to or less than
|
399
|
+
# zero will use event based observation instead of
|
400
|
+
# time based observation.
|
401
|
+
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
402
|
+
# innerHTML should be updated with the
|
403
|
+
# XMLHttpRequest response text.
|
404
|
+
# <tt>:with</tt>:: A JavaScript expression specifying the
|
405
|
+
# parameters for the XMLHttpRequest. This defaults
|
406
|
+
# to 'value', which in the evaluated context
|
407
|
+
# refers to the new field value. If you specify a
|
408
|
+
# string without a "=", it'll be extended to mean
|
409
|
+
# the form key that the value should be assigned to.
|
410
|
+
# So :with => "term" gives "'term'=value". If a "=" is
|
411
|
+
# present, no extension will happen.
|
412
|
+
# <tt>:on</tt>:: Specifies which event handler to observe. By default,
|
413
|
+
# it's set to "changed" for text fields and areas and
|
414
|
+
# "click" for radio buttons and checkboxes. With this,
|
415
|
+
# you can specify it instead to be "blur" or "focus" or
|
416
|
+
# any other event.
|
417
|
+
#
|
418
|
+
# Additionally, you may specify any of the options documented in
|
419
|
+
# link_to_remote.
|
420
|
+
def observe_field(field_id, options = {})
|
421
|
+
if options[:frequency] && options[:frequency] > 0
|
422
|
+
build_observer('Form.Element.Observer', field_id, options)
|
423
|
+
else
|
424
|
+
build_observer('Form.Element.EventObserver', field_id, options)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# Returns the JavaScript needed for a remote function.
|
429
|
+
# Takes the same arguments as link_to_remote.
|
430
|
+
#
|
431
|
+
# Example:
|
432
|
+
# select( :id => "options",
|
433
|
+
# :onChange => remote_function(
|
434
|
+
# :update => "options",
|
435
|
+
# :url => R(Update),
|
436
|
+
# :onSuccess => visual_effect( :highlight, 'my_element' )
|
437
|
+
# )
|
438
|
+
# ) do
|
439
|
+
# option( "Hello", :value => 0 )
|
440
|
+
# option( "World", :value => 1 )
|
441
|
+
# end
|
442
|
+
def remote_function(options)
|
443
|
+
javascript_options = options_for_ajax(options)
|
444
|
+
|
445
|
+
update = ''
|
446
|
+
if options[:update] && options[:update].is_a?(Hash)
|
447
|
+
update = []
|
448
|
+
update << "success:'#{options[:update][:success]}'" if options[:update][:success]
|
449
|
+
update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
|
450
|
+
update = '{' + update.join(',') + '}'
|
451
|
+
elsif options[:update]
|
452
|
+
update << "'#{options[:update]}'"
|
453
|
+
end
|
454
|
+
|
455
|
+
function = update.empty? ?
|
456
|
+
"new Ajax.Request(" :
|
457
|
+
"new Ajax.Updater(#{update}, "
|
458
|
+
|
459
|
+
function << "'#{options[:url]}'"
|
460
|
+
function << ", #{javascript_options})"
|
461
|
+
|
462
|
+
function = "#{options[:before]}; #{function}" if options[:before]
|
463
|
+
function = "#{function}; #{options[:after]}" if options[:after]
|
464
|
+
function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
|
465
|
+
function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
|
466
|
+
|
467
|
+
return function
|
468
|
+
end
|
469
|
+
|
470
|
+
private
|
471
|
+
|
472
|
+
def options_for_javascript(options) #:nodoc:
|
473
|
+
'{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
|
474
|
+
end
|
475
|
+
|
476
|
+
def array_or_string_for_javascript(option)
|
477
|
+
js_option = if option.kind_of?(Array)
|
478
|
+
"['#{option.join('\',\'')}']"
|
479
|
+
elsif !option.nil?
|
480
|
+
"'#{option}'"
|
481
|
+
end
|
482
|
+
js_option
|
483
|
+
end
|
484
|
+
|
485
|
+
def options_for_ajax(options)
|
486
|
+
js_options = build_callbacks(options)
|
487
|
+
|
488
|
+
js_options['asynchronous'] = options[:type] != :synchronous
|
489
|
+
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
|
490
|
+
js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
|
491
|
+
js_options['evalScripts'] = options[:script].nil? || options[:script]
|
492
|
+
|
493
|
+
if options[:form]
|
494
|
+
js_options['parameters'] = 'Form.serialize(this)'
|
495
|
+
elsif options[:submit]
|
496
|
+
js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
|
497
|
+
elsif options[:with]
|
498
|
+
js_options['parameters'] = options[:with]
|
499
|
+
end
|
500
|
+
|
501
|
+
options_for_javascript(js_options)
|
502
|
+
end
|
503
|
+
|
504
|
+
def build_callbacks( options )
|
505
|
+
callbacks = {}
|
506
|
+
options.each do |callback, code|
|
507
|
+
if CALLBACKS.include?(callback)
|
508
|
+
name = 'on' + callback.to_s.capitalize
|
509
|
+
callbacks[name] = "function(request){#{code}}"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
callbacks
|
513
|
+
end
|
514
|
+
|
515
|
+
def build_observer(klass, name, options = {})
|
516
|
+
if options[:with] && !options[:with].include?("=")
|
517
|
+
options[:with] = "'#{options[:with]}=' + value"
|
518
|
+
else
|
519
|
+
options[:with] ||= "'#{name}=' + value" if options[:update]
|
520
|
+
end
|
521
|
+
|
522
|
+
callback = options[:function] || remote_function(options)
|
523
|
+
javascript = "new #{klass}('#{name}', "
|
524
|
+
javascript << "#{options[:frequency]}, " if options[:frequency]
|
525
|
+
javascript << "function(element, value) {"
|
526
|
+
javascript << "#{callback}}"
|
527
|
+
javascript << ", '#{options[:on]}'" if options[:on]
|
528
|
+
javascript << ")"
|
529
|
+
javascript_tag(javascript)
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|