reccenterhq-jquery-rjs 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 850c5c4434113d3ed4ccea45fa1195051b17bdad
4
+ data.tar.gz: 437015ed950c33bf8c1483e29077424862a5334b
5
+ SHA512:
6
+ metadata.gz: 4b42f7bebdb9ec106a87892cccbd42ea0592d1fc125363a0f591350fd30f684916cda7fd3394c53f7e068d349ef4449bf9f555dd8fb8bb0f2110fa99e6009932
7
+ data.tar.gz: 6c4b3c3963e02ecc21c8a6ad643ab094fe019ce7cd1ed6354067241e63074f07c9c1a6e4f5420e10b4dc9d8b285db7c57b64343231d310c878d599a115377f56
data/README ADDED
@@ -0,0 +1,20 @@
1
+ prototype-rails provides Prototype, Scriptaculous, and RJS for Rails 3.1.
2
+
3
+ Prototype and Scriptaculous are provided via the asset pipeline and you
4
+ do *not* need to copy their files into your application. Rails will get
5
+ them from prototype-rails automatically.
6
+
7
+ You may want to add them to your app/assets/javascripts/application.js:
8
+
9
+ //= require prototype
10
+ //= require prototype_ujs
11
+ //= require effects
12
+ //= require dragdrop
13
+ //= require controls
14
+
15
+ New applications using this may also want to add
16
+
17
+ config.action_view.debug_rjs = true
18
+
19
+ to their config/environments/development.rb.
20
+
@@ -0,0 +1,940 @@
1
+ require 'set'
2
+ require 'active_support/json'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/string/output_safety'
5
+
6
+ module ActionView
7
+ # = Action View Prototype Helpers
8
+ module Helpers
9
+ # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
10
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
11
+ # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
12
+ # functionality, and more traditional object-oriented facilities for JavaScript.
13
+ # This module provides a set of helpers to make it more convenient to call
14
+ # functions from Prototype using Rails, including functionality to call remote
15
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
16
+ # This means that you can call actions in your controllers without
17
+ # reloading the page, but still update certain parts of it using
18
+ # injections into the DOM. A common use case is having a form that adds
19
+ # a new element to a list without reloading the page or updating a shopping
20
+ # cart total when a new item is added.
21
+ #
22
+ # == Usage
23
+ # To be able to use these helpers, you must first include the Prototype
24
+ # JavaScript framework in your pages.
25
+ #
26
+ # javascript_include_tag 'prototype'
27
+ #
28
+ # (See the documentation for
29
+ # ActionView::Helpers::JavaScriptHelper for more information on including
30
+ # this and other JavaScript files in your Rails templates.)
31
+ #
32
+ # Now you're ready to call a remote action either through a link...
33
+ #
34
+ # link_to_remote "Add to cart",
35
+ # :url => { :action => "add", :id => product.id },
36
+ # :update => { :success => "cart", :failure => "error" }
37
+ #
38
+ # ...through a form...
39
+ #
40
+ # <%= form_remote_tag :url => '/shipping' do -%>
41
+ # <div><%= submit_tag 'Recalculate Shipping' %></div>
42
+ # <% end -%>
43
+ #
44
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
45
+ # are listed here); check out the documentation for each method to find out more about its usage and options.
46
+ #
47
+ # === Common Options
48
+ # See link_to_remote for documentation of options common to all Ajax
49
+ # helpers; any of the options specified by link_to_remote can be used
50
+ # by the other helpers.
51
+ #
52
+ # == Designing your Rails actions for Ajax
53
+ # When building your action handlers (that is, the Rails actions that receive your background requests), it's
54
+ # important to remember a few things. First, whatever your action would normally return to the browser, it will
55
+ # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
56
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
57
+ # You can turn the layout off on particular actions by doing the following:
58
+ #
59
+ # class SiteController < ActionController::Base
60
+ # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
61
+ # end
62
+ #
63
+ # Optionally, you could do this in the method you wish to lack a layout:
64
+ #
65
+ # render :layout => false
66
+ #
67
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
68
+ # method that Ajax uses to make background requests) method.
69
+ # def name
70
+ # # Is this an XmlHttpRequest request?
71
+ # if (request.xhr?)
72
+ # render :text => @name.to_s
73
+ # else
74
+ # # No? Then render an action.
75
+ # render :action => 'view_attribute', :attr => @name
76
+ # end
77
+ # end
78
+ #
79
+ # The else clause can be left off and the current action will render with full layout and template. An extension
80
+ # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
81
+ #
82
+ # layout proc{ |c| c.request.xhr? ? false : "application" }
83
+ #
84
+ # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
85
+ #
86
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
87
+ # render text output, like this:
88
+ #
89
+ # render :text => 'Return this from my method!'
90
+ #
91
+ # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
92
+ # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
93
+ #
94
+ # == Updating multiple elements
95
+ # See JavaScriptGenerator for information on updating multiple elements
96
+ # on the page in an Ajax response.
97
+ module JqueryHelper
98
+ USE_PROTECTION = const_defined?(:DISABLE_JQUERY_FORGERY_PROTECTION) ? !DISABLE_JQUERY_FORGERY_PROTECTION : true
99
+
100
+ unless const_defined? :JQCALLBACKS
101
+ JQCALLBACKS = Set.new([ :beforeSend, :complete, :error, :success ] + (100..599).to_a)
102
+ #instance_eval { remove_const :AJAX_OPTIONS }
103
+ remove_const(:AJAX_OPTIONS) if const_defined?(:AJAX_OPTIONS)
104
+ AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
105
+ :asynchronous, :method, :insertion, :position,
106
+ :form, :with, :update, :script ]).merge(JQCALLBACKS)
107
+ end
108
+
109
+ # Returns the JavaScript needed for a remote function.
110
+ # See the link_to_remote documentation at https://github.com/rails/prototype_legacy_helper as it takes the same arguments.
111
+ #
112
+ # Example:
113
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
114
+ # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
115
+ # <select id="options" onchange="<%= remote_function(:update => "options",
116
+ # :url => { :action => :update_options }) %>">
117
+ # <option value="0">Hello</option>
118
+ # <option value="1">World</option>
119
+ # </select>
120
+ def remote_function(options)
121
+ javascript_options = options_for_ajax(options)
122
+
123
+ update = ''
124
+ if options[:update] && options[:update].is_a?(Hash)
125
+ update = []
126
+ update << "success:'#{options[:update][:success]}'" if options[:update][:success]
127
+ update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
128
+ update = '{' + update.join(',') + '}'
129
+ elsif options[:update]
130
+ update << "'#{options[:update]}'"
131
+ end
132
+
133
+ function = "#{JQUERY_VAR}.ajax(#{javascript_options})"
134
+
135
+ function = "#{options[:before]}; #{function}" if options[:before]
136
+ function = "#{function}; #{options[:after]}" if options[:after]
137
+ function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
138
+ function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
139
+ return function
140
+ end
141
+
142
+ # All the methods were moved to GeneratorMethods so that
143
+ # #include_helpers_from_context has nothing to overwrite.
144
+ class JavaScriptGenerator #:nodoc:
145
+ class OutputBuffer < Array
146
+ def encoding
147
+ Encoding::UTF_8
148
+ end
149
+ end
150
+ def initialize(context, &block) #:nodoc:
151
+ @context, @lines = context, OutputBuffer.new
152
+ include_helpers_from_context
153
+ @context.with_output_buffer(@lines) do
154
+ @context.instance_exec(self, &block)
155
+ end
156
+ end
157
+
158
+ private
159
+ def include_helpers_from_context
160
+ extend @context.helpers if @context.respond_to?(:helpers) && @context.helpers
161
+ extend GeneratorMethods
162
+ end
163
+
164
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
165
+ # to change the content and presentation of multiple DOM elements. Use
166
+ # this in your Ajax response bodies, either in a <tt>\<script></tt> tag
167
+ # or as plain JavaScript sent with a Content-type of "text/javascript".
168
+ #
169
+ # Create new instances with PrototypeHelper#update_page or with
170
+ # ActionController::Base#render, then call +insert_html+, +replace_html+,
171
+ # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
172
+ # methods on the yielded generator in any order you like to modify the
173
+ # content and appearance of the current page.
174
+ #
175
+ # Example:
176
+ #
177
+ # # Generates:
178
+ # # new Element.insert("list", { bottom: "<li>Some item</li>" });
179
+ # # new Effect.Highlight("list");
180
+ # # ["status-indicator", "cancel-link"].each(Element.hide);
181
+ # update_page do |page|
182
+ # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
183
+ # page.visual_effect :highlight, 'list'
184
+ # page.hide 'status-indicator', 'cancel-link'
185
+ # end
186
+ #
187
+ #
188
+ # Helper methods can be used in conjunction with JavaScriptGenerator.
189
+ # When a helper method is called inside an update block on the +page+
190
+ # object, that method will also have access to a +page+ object.
191
+ #
192
+ # Example:
193
+ #
194
+ # module ApplicationHelper
195
+ # def update_time
196
+ # page.replace_html 'time', Time.now.to_s(:db)
197
+ # page.visual_effect :highlight, 'time'
198
+ # end
199
+ # end
200
+ #
201
+ # # Controller action
202
+ # def poll
203
+ # render(:update) { |page| page.update_time }
204
+ # end
205
+ #
206
+ # Calls to JavaScriptGenerator not matching a helper method below
207
+ # generate a proxy to the JavaScript Class named by the method called.
208
+ #
209
+ # Examples:
210
+ #
211
+ # # Generates:
212
+ # # Foo.init();
213
+ # update_page do |page|
214
+ # page.foo.init
215
+ # end
216
+ #
217
+ # # Generates:
218
+ # # Event.observe('one', 'click', function () {
219
+ # # $('two').show();
220
+ # # });
221
+ # update_page do |page|
222
+ # page.event.observe('one', 'click') do |p|
223
+ # p[:two].show
224
+ # end
225
+ # end
226
+ #
227
+ # You can also use PrototypeHelper#update_page_tag instead of
228
+ # PrototypeHelper#update_page to wrap the generated JavaScript in a
229
+ # <tt>\<script></tt> tag.
230
+ module GeneratorMethods
231
+ def to_s #:nodoc:
232
+ (@lines * $/).tap do |javascript|
233
+ if ActionView::Base.debug_rjs
234
+ source = javascript.dup
235
+ javascript.replace "try {\n#{source}\n} catch (e) "
236
+ javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
237
+ end
238
+ end
239
+ end
240
+
241
+ def jquery_id(id) #:nodoc:
242
+ id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
243
+ end
244
+
245
+ def jquery_ids(ids) #:nodoc:
246
+ Array(ids).map{|id| jquery_id(id)}.join(',')
247
+ end
248
+
249
+ # Returns a element reference by finding it through +id+ in the DOM. This element can then be
250
+ # used for further method calls. Examples:
251
+ #
252
+ # page['blank_slate'] # => $('blank_slate');
253
+ # page['blank_slate'].show # => $('blank_slate').show();
254
+ # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
255
+ #
256
+ # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
257
+ # the correct id:
258
+ #
259
+ # page[@post] # => $('post_45')
260
+ # page[Post.new] # => $('new_post')
261
+ def [](id)
262
+ case id
263
+ when String, Symbol, NilClass
264
+ JavaScriptElementProxy.new(self, id)
265
+ else
266
+ JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
267
+ end
268
+ end
269
+
270
+ # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
271
+ # expression as an argument to another JavaScriptGenerator method.
272
+ def literal(code)
273
+ ::ActiveSupport::JSON::Variable.new(code.to_s)
274
+ end
275
+
276
+ # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
277
+ # used for further method calls. Examples:
278
+ #
279
+ # page.select('p') # => $$('p');
280
+ # page.select('p.welcome b').first # => $$('p.welcome b').first();
281
+ # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
282
+ #
283
+ # You can also use prototype enumerations with the collection. Observe:
284
+ #
285
+ # # Generates: $$('#items li').each(function(value) { value.hide(); });
286
+ # page.select('#items li').each do |value|
287
+ # value.hide
288
+ # end
289
+ #
290
+ # Though you can call the block param anything you want, they are always rendered in the
291
+ # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
292
+ #
293
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
294
+ # page.select('#items li').collect('hidden') do |item|
295
+ # item.hide
296
+ # end
297
+ #
298
+ def select(pattern)
299
+ JavaScriptElementCollectionProxy.new(self, pattern)
300
+ end
301
+
302
+ # Inserts HTML at the specified +position+ relative to the DOM element
303
+ # identified by the given +id+.
304
+ #
305
+ # +position+ may be one of:
306
+ #
307
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
308
+ # element's existing content.
309
+ # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
310
+ # element's existing content.
311
+ # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
312
+ # <tt>:after</tt>:: HTML is inserted immediately following the element.
313
+ #
314
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
315
+ # of options to be passed to ActionView::Base#render. For example:
316
+ #
317
+ # # Insert the rendered 'navigation' partial just before the DOM
318
+ # # element with ID 'content'.
319
+ # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
320
+ # page.insert_html :before, 'content', :partial => 'navigation'
321
+ #
322
+ # # Add a list item to the bottom of the <ul> with ID 'list'.
323
+ # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
324
+ # page.insert_html :bottom, 'list', '<li>Last item</li>'
325
+ #
326
+ def insert_html(position, id, *options_for_render)
327
+ insertion = position.to_s.downcase
328
+ insertion = 'append' if insertion == 'bottom'
329
+ insertion = 'prepend' if insertion == 'top'
330
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").#{insertion}", render(*options_for_render)
331
+ # content = javascript_object_for(render(*options_for_render))
332
+ # record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
333
+ end
334
+
335
+ # Replaces the inner HTML of the DOM element with the given +id+.
336
+ #
337
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
338
+ # of options to be passed to ActionView::Base#render. For example:
339
+ #
340
+ # # Replace the HTML of the DOM element having ID 'person-45' with the
341
+ # # 'person' partial for the appropriate object.
342
+ # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
343
+ # page.replace_html 'person-45', :partial => 'person', :object => @person
344
+ #
345
+ def replace_html(id, *options_for_render)
346
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").html", render(*options_for_render)
347
+ # call 'Element.update', id, render(*options_for_render)
348
+ end
349
+
350
+ # Replaces the "outer HTML" (i.e., the entire element, not just its
351
+ # contents) of the DOM element with the given +id+.
352
+ #
353
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
354
+ # of options to be passed to ActionView::Base#render. For example:
355
+ #
356
+ # # Replace the DOM element having ID 'person-45' with the
357
+ # # 'person' partial for the appropriate object.
358
+ # page.replace 'person-45', :partial => 'person', :object => @person
359
+ #
360
+ # This allows the same partial that is used for the +insert_html+ to
361
+ # be also used for the input to +replace+ without resorting to
362
+ # the use of wrapper elements.
363
+ #
364
+ # Examples:
365
+ #
366
+ # <div id="people">
367
+ # <%= render :partial => 'person', :collection => @people %>
368
+ # </div>
369
+ #
370
+ # # Insert a new person
371
+ # #
372
+ # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
373
+ # page.insert_html :bottom, :partial => 'person', :object => @person
374
+ #
375
+ # # Replace an existing person
376
+ #
377
+ # # Generates: Element.replace("person_45", "-- Contents of partial --");
378
+ # page.replace 'person_45', :partial => 'person', :object => @person
379
+ #
380
+ def replace(id, *options_for_render)
381
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").replaceWith", render(*options_for_render)
382
+ #call 'Element.replace', id, render(*options_for_render)
383
+ end
384
+
385
+ # Removes the DOM elements with the given +ids+ from the page.
386
+ #
387
+ # Example:
388
+ #
389
+ # # Remove a few people
390
+ # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
391
+ # page.remove 'person_23', 'person_9', 'person_2'
392
+ #
393
+ def remove(*ids)
394
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").remove"
395
+ #loop_on_multiple_args 'Element.remove', ids
396
+ end
397
+
398
+ # Shows hidden DOM elements with the given +ids+.
399
+ #
400
+ # Example:
401
+ #
402
+ # # Show a few people
403
+ # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
404
+ # page.show 'person_6', 'person_13', 'person_223'
405
+ #
406
+ def show(*ids)
407
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").show"
408
+ #loop_on_multiple_args 'Element.show', ids
409
+ end
410
+
411
+ # Hides the visible DOM elements with the given +ids+.
412
+ #
413
+ # Example:
414
+ #
415
+ # # Hide a few people
416
+ # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
417
+ # page.hide 'person_29', 'person_9', 'person_0'
418
+ #
419
+ def hide(*ids)
420
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").hide"
421
+ #loop_on_multiple_args 'Element.hide', ids
422
+ end
423
+
424
+ # Toggles the visibility of the DOM elements with the given +ids+.
425
+ # Example:
426
+ #
427
+ # # Show a few people
428
+ # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
429
+ # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
430
+ # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
431
+ #
432
+ def toggle(*ids)
433
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").toggle"
434
+ #loop_on_multiple_args 'Element.toggle', ids
435
+ end
436
+
437
+ # Displays an alert dialog with the given +message+.
438
+ #
439
+ # Example:
440
+ #
441
+ # # Generates: alert('This message is from Rails!')
442
+ # page.alert('This message is from Rails!')
443
+ def alert(message)
444
+ call 'alert', message
445
+ end
446
+
447
+ # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
448
+ #
449
+ # Examples:
450
+ #
451
+ # # Generates: window.location.href = "/mycontroller";
452
+ # page.redirect_to(:action => 'index')
453
+ #
454
+ # # Generates: window.location.href = "/account/signup";
455
+ # page.redirect_to(:controller => 'account', :action => 'signup')
456
+ def redirect_to(location)
457
+ url = location.is_a?(String) ? location : @context.url_for(location)
458
+ record "window.location.href = #{url.inspect}"
459
+ end
460
+
461
+ # Reloads the browser's current +location+ using JavaScript
462
+ #
463
+ # Examples:
464
+ #
465
+ # # Generates: window.location.reload();
466
+ # page.reload
467
+ def reload
468
+ record 'window.location.reload()'
469
+ end
470
+
471
+ # Calls the JavaScript +function+, optionally with the given +arguments+.
472
+ #
473
+ # If a block is given, the block will be passed to a new JavaScriptGenerator;
474
+ # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
475
+ # and passed as the called function's final argument.
476
+ #
477
+ # Examples:
478
+ #
479
+ # # Generates: Element.replace(my_element, "My content to replace with.")
480
+ # page.call 'Element.replace', 'my_element', "My content to replace with."
481
+ #
482
+ # # Generates: alert('My message!')
483
+ # page.call 'alert', 'My message!'
484
+ #
485
+ # # Generates:
486
+ # # my_method(function() {
487
+ # # $("one").show();
488
+ # # $("two").hide();
489
+ # # });
490
+ # page.call(:my_method) do |p|
491
+ # p[:one].show
492
+ # p[:two].hide
493
+ # end
494
+ def call(function, *arguments, &block)
495
+ record "#{function}(#{arguments_for_call(arguments, block)})"
496
+ end
497
+
498
+ # Assigns the JavaScript +variable+ the given +value+.
499
+ #
500
+ # Examples:
501
+ #
502
+ # # Generates: my_string = "This is mine!";
503
+ # page.assign 'my_string', 'This is mine!'
504
+ #
505
+ # # Generates: record_count = 33;
506
+ # page.assign 'record_count', 33
507
+ #
508
+ # # Generates: tabulated_total = 47
509
+ # page.assign 'tabulated_total', @total_from_cart
510
+ #
511
+ def assign(variable, value)
512
+ record "#{variable} = #{javascript_object_for(value)}"
513
+ end
514
+
515
+ # Writes raw JavaScript to the page.
516
+ #
517
+ # Example:
518
+ #
519
+ # page << "alert('JavaScript with Prototype.');"
520
+ def <<(javascript)
521
+ @lines << javascript
522
+ end
523
+
524
+ # Executes the content of the block after a delay of +seconds+. Example:
525
+ #
526
+ # # Generates:
527
+ # # setTimeout(function() {
528
+ # # ;
529
+ # # new Effect.Fade("notice",{});
530
+ # # }, 20000);
531
+ # page.delay(20) do
532
+ # page.visual_effect :fade, 'notice'
533
+ # end
534
+ def delay(seconds = 1)
535
+ record "setTimeout(function() {\n\n"
536
+ yield
537
+ record "}, #{(seconds * 1000).to_i})"
538
+ end
539
+
540
+ def arguments_for_call(arguments, block = nil)
541
+ arguments << block_to_function(block) if block
542
+ arguments.map { |argument| javascript_object_for(argument) }.join ', '
543
+ end
544
+
545
+ private
546
+ def loop_on_multiple_args(method, ids)
547
+ record(ids.size>1 ?
548
+ "#{javascript_object_for(ids)}.each(#{method})" :
549
+ "#{method}(#{javascript_object_for(ids.first)})")
550
+ end
551
+
552
+ def page
553
+ self
554
+ end
555
+
556
+ def record(line)
557
+ line = "#{line.to_s.chomp.gsub(/\;\z/, '')};".html_safe
558
+ self << line
559
+ line
560
+ end
561
+
562
+ def render(*options)
563
+ with_formats(:html) do
564
+ case option = options.first
565
+ when Hash
566
+ @context.render(*options)
567
+ else
568
+ option.to_s
569
+ end
570
+ end
571
+ end
572
+
573
+ def with_formats(*args)
574
+ return yield unless @context
575
+
576
+ lookup = @context.lookup_context
577
+ begin
578
+ old_formats, lookup.formats = lookup.formats, args
579
+ yield
580
+ ensure
581
+ lookup.formats = old_formats
582
+ end
583
+ end
584
+
585
+ def javascript_object_for(object)
586
+ ::ActiveSupport::JSON.encode(object)
587
+ end
588
+
589
+ def block_to_function(block)
590
+ generator = self.class.new(@context, &block)
591
+ literal("function() { #{generator.to_s} }")
592
+ end
593
+
594
+ def method_missing(method, *arguments)
595
+ JavaScriptProxy.new(self, method.to_s.camelize)
596
+ end
597
+ end
598
+ end
599
+
600
+ # Yields a JavaScriptGenerator and returns the generated JavaScript code.
601
+ # Use this to update multiple elements on a page in an Ajax response.
602
+ # See JavaScriptGenerator for more information.
603
+ #
604
+ # Example:
605
+ #
606
+ # update_page do |page|
607
+ # page.hide 'spinner'
608
+ # end
609
+ def update_page(&block)
610
+ JavaScriptGenerator.new(self, &block).to_s.html_safe
611
+ end
612
+
613
+ # Works like update_page but wraps the generated JavaScript in a
614
+ # <tt>\<script></tt> tag. Use this to include generated JavaScript in an
615
+ # ERb template. See JavaScriptGenerator for more information.
616
+ #
617
+ # +html_options+ may be a hash of <tt>\<script></tt> attributes to be
618
+ # passed to ActionView::Helpers::JavaScriptHelper#javascript_tag.
619
+ def update_page_tag(html_options = {}, &block)
620
+ javascript_tag update_page(&block), html_options
621
+ end
622
+
623
+ protected
624
+ def options_for_javascript(options)
625
+ if options.empty?
626
+ '{}'
627
+ else
628
+ "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
629
+ end
630
+ end
631
+
632
+ def options_for_ajax(options)
633
+ js_options = build_callbacks(options)
634
+
635
+ url_options = options[:url]
636
+ url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
637
+ js_options['url'] = "'#{url_for(url_options)}'"
638
+ js_options['async'] = false if options[:type] == :synchronous
639
+ js_options['type'] = options[:method] ? method_option_to_s(options[:method]) : ( options[:form] ? "'post'" : nil )
640
+ js_options['dataType'] = options[:datatype] ? "'#{options[:datatype]}'" : (options[:update] ? nil : "'script'")
641
+
642
+ if options[:form]
643
+ js_options['data'] = "#{JQUERY_VAR}.param(#{JQUERY_VAR}(this).serializeArray())"
644
+ elsif options[:submit]
645
+ js_options['data'] = "#{JQUERY_VAR}(\"##{options[:submit]} :input\").serialize()"
646
+ elsif options[:with]
647
+ js_options['data'] = options[:with].gsub("Form.serialize(this.form)","#{JQUERY_VAR}.param(#{JQUERY_VAR}(this.form).serializeArray())")
648
+ end
649
+
650
+ js_options['type'] ||= "'post'"
651
+ if options[:method]
652
+ if method_option_to_s(options[:method]) == "'put'" || method_option_to_s(options[:method]) == "'delete'"
653
+ js_options['type'] = "'post'"
654
+ if js_options['data']
655
+ js_options['data'] << " + '&"
656
+ else
657
+ js_options['data'] = "'"
658
+ end
659
+ js_options['data'] << "_method=#{options[:method]}'"
660
+ end
661
+ end
662
+
663
+ if USE_PROTECTION && respond_to?('protect_against_forgery?') && protect_against_forgery?
664
+ if js_options['data']
665
+ js_options['data'] << " + '&"
666
+ else
667
+ js_options['data'] = "'"
668
+ end
669
+ js_options['data'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
670
+ end
671
+ js_options['data'] = "''" if js_options['type'] == "'post'" && js_options['data'].nil?
672
+ options_for_javascript(js_options.reject {|key, value| value.nil?})
673
+ end
674
+
675
+ def build_update_for_success(html_id, insertion=nil)
676
+ insertion = build_insertion(insertion)
677
+ "#{JQUERY_VAR}('#{jquery_id(html_id)}').#{insertion}(request);"
678
+ end
679
+
680
+ def build_update_for_error(html_id, insertion=nil)
681
+ insertion = build_insertion(insertion)
682
+ "#{JQUERY_VAR}('#{jquery_id(html_id)}').#{insertion}(request.responseText);"
683
+ end
684
+
685
+ def build_insertion(insertion)
686
+ insertion = insertion ? insertion.to_s.downcase : 'html'
687
+ insertion = 'append' if insertion == 'bottom'
688
+ insertion = 'prepend' if insertion == 'top'
689
+ insertion
690
+ end
691
+
692
+ def method_option_to_s(method)
693
+ (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
694
+ end
695
+
696
+ def build_callbacks(options)
697
+ callbacks = {}
698
+ options[:beforeSend] = '';
699
+ [:uninitialized,:loading].each do |key|
700
+ options[:beforeSend] << (options[key].last == ';' ? options.delete(key) : options.delete(key) << ';') if options[key]
701
+ end
702
+ options.delete(:beforeSend) if options[:beforeSend].blank?
703
+ options[:complete] = options.delete(:loaded) if options[:loaded]
704
+ options[:error] = options.delete(:failure) if options[:failure]
705
+ if options[:update]
706
+ if options[:update].is_a?(Hash)
707
+ options[:update][:error] = options[:update].delete(:failure) if options[:update][:failure]
708
+ if options[:update][:success]
709
+ options[:success] = build_update_for_success(options[:update][:success], options[:position]) << (options[:success] ? options[:success] : '')
710
+ end
711
+ if options[:update][:error]
712
+ options[:error] = build_update_for_error(options[:update][:error], options[:position]) << (options[:error] ? options[:error] : '')
713
+ end
714
+ else
715
+ options[:success] = build_update_for_success(options[:update], options[:position]) << (options[:success] ? options[:success] : '')
716
+ end
717
+ end
718
+ options.each do |callback, code|
719
+ if JQCALLBACKS.include?(callback)
720
+ callbacks[callback] = "function(request){#{code}}"
721
+ end
722
+ end
723
+ callbacks
724
+ end
725
+ end
726
+
727
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
728
+ class JavaScriptProxy < (Rails::VERSION::MAJOR >= 4) ? ::ActiveSupport::ProxyObject : ::ActiveSupport::BasicObject #:nodoc:
729
+
730
+ def initialize(generator, root = nil)
731
+ @generator = generator
732
+ @generator << root.html_safe if root
733
+ end
734
+
735
+ def is_a?(klass)
736
+ klass == JavaScriptProxy
737
+ end
738
+
739
+ private
740
+ def method_missing(method, *arguments, &block)
741
+ if method.to_s =~ /(.*)=$/
742
+ assign($1, arguments.first)
743
+ else
744
+ call("#{method.to_s.camelize(:lower)}", *arguments, &block)
745
+ end
746
+ end
747
+
748
+ def call(function, *arguments, &block)
749
+ append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
750
+ self
751
+ end
752
+
753
+ def assign(variable, value)
754
+ append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
755
+ end
756
+
757
+ def function_chain
758
+ @function_chain ||= @generator.instance_variable_get(:@lines)
759
+ end
760
+
761
+ def append_to_function_chain!(call)
762
+ function_chain[-1].chomp!(';')
763
+ function_chain[-1] += ".#{call};"
764
+ end
765
+ end
766
+
767
+ class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
768
+ def initialize(generator, id)
769
+ id = id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
770
+ @id = id
771
+ super(generator, "#{::JQUERY_VAR}(#{::ActiveSupport::JSON.encode(id)})".html_safe)
772
+ end
773
+
774
+ # Allows access of element attributes through +attribute+. Examples:
775
+ #
776
+ # page['foo']['style'] # => $('foo').style;
777
+ # page['foo']['style']['color'] # => $('blank_slate').style.color;
778
+ # page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
779
+ # page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
780
+ def [](attribute)
781
+ append_to_function_chain!(attribute)
782
+ self
783
+ end
784
+
785
+ def []=(variable, value)
786
+ assign(variable, value)
787
+ end
788
+
789
+ def replace_html(*options_for_render)
790
+ call 'html', @generator.send(:render, *options_for_render)
791
+ end
792
+
793
+ def replace(*options_for_render)
794
+ call 'replaceWith', @generator.send(:render, *options_for_render)
795
+ end
796
+
797
+ def reload(options_for_replace = {})
798
+ replace(options_for_replace.merge({ :partial => @id.to_s.sub(/^#/,'') }))
799
+ end
800
+
801
+ def value()
802
+ call 'val()'
803
+ end
804
+
805
+ def value=(value)
806
+ call 'val', value
807
+ end
808
+
809
+ end
810
+
811
+ class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
812
+ def initialize(generator, variable)
813
+ @variable = ::ActiveSupport::JSON::Variable.new(variable)
814
+ @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
815
+ super(generator)
816
+ end
817
+
818
+ # The JSON Encoder calls this to check for the +to_json+ method
819
+ # Since it's a blank slate object, I suppose it responds to anything.
820
+ def respond_to?(*)
821
+ true
822
+ end
823
+
824
+ def as_json(options = nil)
825
+ @variable
826
+ end
827
+
828
+ private
829
+ def append_to_function_chain!(call)
830
+ @generator << @variable if @empty
831
+ @empty = false
832
+ super
833
+ end
834
+ end
835
+
836
+ class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
837
+ ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by, :in_groups_of, :each_slice] unless defined? ENUMERABLE_METHODS_WITH_RETURN
838
+ ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
839
+ attr_reader :generator
840
+ delegate :arguments_for_call, :to => :generator
841
+
842
+ def initialize(generator, pattern)
843
+ super(generator, @pattern = pattern)
844
+ end
845
+
846
+ def each_slice(variable, number, &block)
847
+ if block
848
+ enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
849
+ else
850
+ add_variable_assignment!(variable)
851
+ append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
852
+ end
853
+ end
854
+
855
+ def grep(variable, pattern, &block)
856
+ enumerate :grep, :variable => variable, :return => true, :method_args => [::ActiveSupport::JSON::Variable.new(pattern.inspect)], :yield_args => %w(value index), &block
857
+ end
858
+
859
+ def in_groups_of(variable, number, fill_with = nil)
860
+ arguments = [number]
861
+ arguments << fill_with unless fill_with.nil?
862
+ add_variable_assignment!(variable)
863
+ append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
864
+ end
865
+
866
+ def inject(variable, memo, &block)
867
+ enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
868
+ end
869
+
870
+ def pluck(variable, property)
871
+ add_variable_assignment!(variable)
872
+ append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
873
+ end
874
+
875
+ def zip(variable, *arguments, &block)
876
+ add_variable_assignment!(variable)
877
+ append_enumerable_function!("zip(#{arguments_for_call arguments}")
878
+ if block
879
+ function_chain[-1] += ", function(array) {"
880
+ yield ::ActiveSupport::JSON::Variable.new('array')
881
+ add_return_statement!
882
+ @generator << '});'
883
+ else
884
+ function_chain[-1] += ');'
885
+ end
886
+ end
887
+
888
+ private
889
+ def method_missing(method, *arguments, &block)
890
+ if ENUMERABLE_METHODS.include?(method)
891
+ returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
892
+ variable = arguments.first if returnable
893
+ enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
894
+ else
895
+ super
896
+ end
897
+ end
898
+
899
+ # Options
900
+ # * variable - name of the variable to set the result of the enumeration to
901
+ # * method_args - array of the javascript enumeration method args that occur before the function
902
+ # * yield_args - array of the javascript yield args
903
+ # * return - true if the enumeration should return the last statement
904
+ def enumerate(enumerable, options = {}, &block)
905
+ options[:method_args] ||= []
906
+ options[:yield_args] ||= []
907
+ yield_args = options[:yield_args] * ', '
908
+ method_args = arguments_for_call options[:method_args] # foo, bar, function
909
+ method_args << ', ' unless method_args.blank?
910
+ add_variable_assignment!(options[:variable]) if options[:variable]
911
+ append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
912
+ # only yield as many params as were passed in the block
913
+ yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
914
+ add_return_statement! if options[:return]
915
+ @generator << '});'
916
+ end
917
+
918
+ def add_variable_assignment!(variable)
919
+ function_chain.push("var #{variable} = #{function_chain.pop}")
920
+ end
921
+
922
+ def add_return_statement!
923
+ unless function_chain.last =~ /return/
924
+ function_chain.push("return #{function_chain.pop.chomp(';')};")
925
+ end
926
+ end
927
+
928
+ def append_enumerable_function!(call)
929
+ function_chain[-1].chomp!(';')
930
+ function_chain[-1] += ".#{call}"
931
+ end
932
+ end
933
+
934
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
935
+ def initialize(generator, pattern)
936
+ super(generator, "#{::JQUERY_VAR}(#{::ActiveSupport::JSON.encode(pattern)})")
937
+ end
938
+ end
939
+ end
940
+ end