de_rjs 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d660448d4fe1823a9f71d6a2f4f4ba5d1e38ac95
4
+ data.tar.gz: bd42c4e907d97a136d75939823d7371f5aac7b47
5
+ SHA512:
6
+ metadata.gz: d86d3dfc21d72253e42118f5034a533d70fd1d7c52e20164bb6a9afe69c70763d0acb2ccc4a3e6814eab8e3e45334a70481845b6221bbb6b2c13b9ddfca9da89
7
+ data.tar.gz: ce15d413bdc07281b20cc5a4ef8110c3927b6b3ee4cd42470496b3bdff159c72bd03ab2fd3708097360fe130678a9ffb7cbf0a2ee383648a89aee7e46abe78a2
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README ADDED
@@ -0,0 +1,3 @@
1
+ Use this to de-RJS your application.
2
+
3
+ Converts your .rjs code into js.erb compliant code
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.test_files = Dir.glob('test/*_test.rb') + Dir.glob('test/{controller,template}/**/*_test.rb')
8
+ t.warning = true
9
+ t.verbose = true
10
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
4
+
5
+ require 'de_rjs/runner'
6
+ DeRjs::Runner.new(ARGV).execute
@@ -0,0 +1,12 @@
1
+ require 'rails'
2
+ require 'active_support'
3
+
4
+ unless defined? JQUERY_VAR
5
+ JQUERY_VAR = 'jQuery'
6
+ end
7
+
8
+ module DeRjs
9
+ end
10
+
11
+ require 'de_rjs/jquery_generator'
12
+ require 'de_rjs/rewriter'
@@ -0,0 +1,789 @@
1
+ unless defined? JQUERY_VAR
2
+ JQUERY_VAR = 'jQuery'
3
+ end
4
+
5
+ module DeRjs
6
+ # A string that returns itself as its JSON-encoded form.
7
+ class JsonLiteral < String
8
+ def as_json(options = nil) self end #:nodoc:
9
+ def encode_json(encoder) self end #:nodoc:
10
+ end
11
+
12
+ class JqueryGenerator #:nodoc:
13
+ class OutputBuffer < Array
14
+ def encoding
15
+ Encoding::UTF_8
16
+ end
17
+ end
18
+ def initialize(context, &block) #:nodoc:
19
+ @context, @lines = context, OutputBuffer.new
20
+ include_helpers_from_context
21
+ #@context.with_output_buffer(@lines) do
22
+ #@context.instance_exec(self, &block)
23
+ #end
24
+ self.instance_exec(&block)
25
+ end
26
+
27
+ private
28
+ def include_helpers_from_context
29
+ #extend @context.helpers if @context.respond_to?(:helpers) && @context.helpers
30
+ extend GeneratorMethods
31
+ end
32
+
33
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
34
+ # to change the content and presentation of multiple DOM elements. Use
35
+ # this in your Ajax response bodies, either in a <tt>\<script></tt> tag
36
+ # or as plain JavaScript sent with a Content-type of "text/javascript".
37
+ #
38
+ # Create new instances with PrototypeHelper#update_page or with
39
+ # ActionController::Base#render, then call +insert_html+, +replace_html+,
40
+ # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
41
+ # methods on the yielded generator in any order you like to modify the
42
+ # content and appearance of the current page.
43
+ #
44
+ # Example:
45
+ #
46
+ # # Generates:
47
+ # # new Element.insert("list", { bottom: "<li>Some item</li>" });
48
+ # # new Effect.Highlight("list");
49
+ # # ["status-indicator", "cancel-link"].each(Element.hide);
50
+ # update_page do |page|
51
+ # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
52
+ # page.visual_effect :highlight, 'list'
53
+ # page.hide 'status-indicator', 'cancel-link'
54
+ # end
55
+ #
56
+ #
57
+ # Helper methods can be used in conjunction with JavaScriptGenerator.
58
+ # When a helper method is called inside an update block on the +page+
59
+ # object, that method will also have access to a +page+ object.
60
+ #
61
+ # Example:
62
+ #
63
+ # module ApplicationHelper
64
+ # def update_time
65
+ # page.replace_html 'time', Time.now.to_s(:db)
66
+ # page.visual_effect :highlight, 'time'
67
+ # end
68
+ # end
69
+ #
70
+ # # Controller action
71
+ # def poll
72
+ # render(:update) { |page| page.update_time }
73
+ # end
74
+ #
75
+ # Calls to JavaScriptGenerator not matching a helper method below
76
+ # generate a proxy to the JavaScript Class named by the method called.
77
+ #
78
+ # Examples:
79
+ #
80
+ # # Generates:
81
+ # # Foo.init();
82
+ # update_page do |page|
83
+ # page.foo.init
84
+ # end
85
+ #
86
+ # # Generates:
87
+ # # Event.observe('one', 'click', function () {
88
+ # # $('two').show();
89
+ # # });
90
+ # update_page do |page|
91
+ # page.event.observe('one', 'click') do |p|
92
+ # p[:two].show
93
+ # end
94
+ # end
95
+ #
96
+ # You can also use PrototypeHelper#update_page_tag instead of
97
+ # PrototypeHelper#update_page to wrap the generated JavaScript in a
98
+ # <tt>\<script></tt> tag.
99
+ module GeneratorMethods
100
+ def to_s #:nodoc:
101
+ #(@lines * $/).tap do |javascript|
102
+ #if ActionView::Base.debug_rjs
103
+ #source = javascript.dup
104
+ #javascript.replace "try {\n#{source}\n} catch (e) "
105
+ #javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
106
+ #end
107
+ #end
108
+
109
+ @lines * $/
110
+ end
111
+
112
+ def jquery_id(id) #:nodoc:
113
+ id.sub(/<%=.*%>/,'').to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
114
+ end
115
+
116
+ def jquery_ids(ids) #:nodoc:
117
+ Array(ids).map{|id| jquery_id(id)}.join(',')
118
+ end
119
+
120
+ # Returns a element reference by finding it through +id+ in the DOM. This element can then be
121
+ # used for further method calls. Examples:
122
+ #
123
+ # page['blank_slate'] # => $('blank_slate');
124
+ # page['blank_slate'].show # => $('blank_slate').show();
125
+ # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
126
+ #
127
+ # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
128
+ # the correct id:
129
+ #
130
+ # page[@post] # => $('post_45')
131
+ # page[Post.new] # => $('new_post')
132
+ def [](id)
133
+ case id
134
+ when String, Symbol, NilClass
135
+ JavaScriptElementProxy.new(self, id)
136
+ else
137
+ JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
138
+ end
139
+ end
140
+
141
+ # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
142
+ # expression as an argument to another JavaScriptGenerator method.
143
+ def literal(code)
144
+ JsonLiteral.new(code.to_s)
145
+ end
146
+
147
+ # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
148
+ # used for further method calls. Examples:
149
+ #
150
+ # page.select('p') # => $$('p');
151
+ # page.select('p.welcome b').first # => $$('p.welcome b').first();
152
+ # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
153
+ #
154
+ # You can also use prototype enumerations with the collection. Observe:
155
+ #
156
+ # # Generates: $$('#items li').each(function(value) { value.hide(); });
157
+ # page.select('#items li').each do |value|
158
+ # value.hide
159
+ # end
160
+ #
161
+ # Though you can call the block param anything you want, they are always rendered in the
162
+ # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
163
+ #
164
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
165
+ # page.select('#items li').collect('hidden') do |item|
166
+ # item.hide
167
+ # end
168
+ #
169
+ def select(pattern)
170
+ JavaScriptElementCollectionProxy.new(self, pattern)
171
+ end
172
+
173
+ # Inserts HTML at the specified +position+ relative to the DOM element
174
+ # identified by the given +id+.
175
+ #
176
+ # +position+ may be one of:
177
+ #
178
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
179
+ # element's existing content.
180
+ # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
181
+ # element's existing content.
182
+ # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
183
+ # <tt>:after</tt>:: HTML is inserted immediately following the element.
184
+ #
185
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
186
+ # of options to be passed to ActionView::Base#render. For example:
187
+ #
188
+ # # Insert the rendered 'navigation' partial just before the DOM
189
+ # # element with ID 'content'.
190
+ # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
191
+ # page.insert_html :before, 'content', :partial => 'navigation'
192
+ #
193
+ # # Add a list item to the bottom of the <ul> with ID 'list'.
194
+ # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
195
+ # page.insert_html :bottom, 'list', '<li>Last item</li>'
196
+ #
197
+ def insert_html(position, id, *options_for_render)
198
+ insertion = position.to_s.downcase
199
+ insertion = 'append' if insertion == 'bottom'
200
+ insertion = 'prepend' if insertion == 'top'
201
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").#{insertion}", render(*options_for_render)
202
+ # content = javascript_object_for(render(*options_for_render))
203
+ # record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
204
+ end
205
+
206
+ # Replaces the inner HTML of the DOM element with the given +id+.
207
+ #
208
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
209
+ # of options to be passed to ActionView::Base#render. For example:
210
+ #
211
+ # # Replace the HTML of the DOM element having ID 'person-45' with the
212
+ # # 'person' partial for the appropriate object.
213
+ # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
214
+ # page.replace_html 'person-45', :partial => 'person', :object => @person
215
+ #
216
+ def replace_html(id, *options_for_render)
217
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").html", render(*options_for_render)
218
+ # call 'Element.update', id, render(*options_for_render)
219
+ end
220
+
221
+ # Replaces the "outer HTML" (i.e., the entire element, not just its
222
+ # contents) of the DOM element with the given +id+.
223
+ #
224
+ # +options_for_render+ may be either a string of HTML to insert, or a hash
225
+ # of options to be passed to ActionView::Base#render. For example:
226
+ #
227
+ # # Replace the DOM element having ID 'person-45' with the
228
+ # # 'person' partial for the appropriate object.
229
+ # page.replace 'person-45', :partial => 'person', :object => @person
230
+ #
231
+ # This allows the same partial that is used for the +insert_html+ to
232
+ # be also used for the input to +replace+ without resorting to
233
+ # the use of wrapper elements.
234
+ #
235
+ # Examples:
236
+ #
237
+ # <div id="people">
238
+ # <%= render :partial => 'person', :collection => @people %>
239
+ # </div>
240
+ #
241
+ # # Insert a new person
242
+ # #
243
+ # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
244
+ # page.insert_html :bottom, :partial => 'person', :object => @person
245
+ #
246
+ # # Replace an existing person
247
+ #
248
+ # # Generates: Element.replace("person_45", "-- Contents of partial --");
249
+ # page.replace 'person_45', :partial => 'person', :object => @person
250
+ #
251
+ def replace(id, *options_for_render)
252
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").replaceWith", render(*options_for_render)
253
+ #call 'Element.replace', id, render(*options_for_render)
254
+ end
255
+
256
+ # Removes the DOM elements with the given +ids+ from the page.
257
+ #
258
+ # Example:
259
+ #
260
+ # # Remove a few people
261
+ # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
262
+ # page.remove 'person_23', 'person_9', 'person_2'
263
+ #
264
+ def remove(*ids)
265
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").remove"
266
+ #loop_on_multiple_args 'Element.remove', ids
267
+ end
268
+
269
+ # Shows hidden DOM elements with the given +ids+.
270
+ #
271
+ # Example:
272
+ #
273
+ # # Show a few people
274
+ # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
275
+ # page.show 'person_6', 'person_13', 'person_223'
276
+ #
277
+ def show(*ids)
278
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").show"
279
+ #loop_on_multiple_args 'Element.show', ids
280
+ end
281
+
282
+ # Hides the visible DOM elements with the given +ids+.
283
+ #
284
+ # Example:
285
+ #
286
+ # # Hide a few people
287
+ # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
288
+ # page.hide 'person_29', 'person_9', 'person_0'
289
+ #
290
+ def hide(*ids)
291
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").hide"
292
+ #loop_on_multiple_args 'Element.hide', ids
293
+ end
294
+
295
+ # Toggles the visibility of the DOM elements with the given +ids+.
296
+ # Example:
297
+ #
298
+ # # Show a few people
299
+ # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
300
+ # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
301
+ # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
302
+ #
303
+ def toggle(*ids)
304
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").toggle"
305
+ #loop_on_multiple_args 'Element.toggle', ids
306
+ end
307
+
308
+ # Displays an alert dialog with the given +message+.
309
+ #
310
+ # Example:
311
+ #
312
+ # # Generates: alert('This message is from Rails!')
313
+ # page.alert('This message is from Rails!')
314
+ def alert(message)
315
+ call 'alert', message
316
+ end
317
+
318
+ # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
319
+ #
320
+ # Examples:
321
+ #
322
+ # # Generates: window.location.href = "/mycontroller";
323
+ # page.redirect_to(:action => 'index')
324
+ #
325
+ # # Generates: window.location.href = "/account/signup";
326
+ # page.redirect_to(:controller => 'account', :action => 'signup')
327
+ def redirect_to(location)
328
+ #url = location.is_a?(String) ? location : @context.url_for(location)
329
+ url = location.to_s
330
+ record "window.location.href = #{url.inspect}"
331
+ end
332
+
333
+ # Reloads the browser's current +location+ using JavaScript
334
+ #
335
+ # Examples:
336
+ #
337
+ # # Generates: window.location.reload();
338
+ # page.reload
339
+ def reload
340
+ record 'window.location.reload()'
341
+ end
342
+
343
+ # Calls the JavaScript +function+, optionally with the given +arguments+.
344
+ #
345
+ # If a block is given, the block will be passed to a new JavaScriptGenerator;
346
+ # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
347
+ # and passed as the called function's final argument.
348
+ #
349
+ # Examples:
350
+ #
351
+ # # Generates: Element.replace(my_element, "My content to replace with.")
352
+ # page.call 'Element.replace', 'my_element', "My content to replace with."
353
+ #
354
+ # # Generates: alert('My message!')
355
+ # page.call 'alert', 'My message!'
356
+ #
357
+ # # Generates:
358
+ # # my_method(function() {
359
+ # # $("one").show();
360
+ # # $("two").hide();
361
+ # # });
362
+ # page.call(:my_method) do |p|
363
+ # p[:one].show
364
+ # p[:two].hide
365
+ # end
366
+ def call(function, *arguments, &block)
367
+ record "#{function}(#{arguments_for_call(arguments, block)})"
368
+ end
369
+
370
+ # Assigns the JavaScript +variable+ the given +value+.
371
+ #
372
+ # Examples:
373
+ #
374
+ # # Generates: my_string = "This is mine!";
375
+ # page.assign 'my_string', 'This is mine!'
376
+ #
377
+ # # Generates: record_count = 33;
378
+ # page.assign 'record_count', 33
379
+ #
380
+ # # Generates: tabulated_total = 47
381
+ # page.assign 'tabulated_total', @total_from_cart
382
+ #
383
+ def assign(variable, value)
384
+ record "#{variable} = #{javascript_object_for(value)}"
385
+ end
386
+
387
+ # Writes raw JavaScript to the page.
388
+ #
389
+ # Example:
390
+ #
391
+ # page << "alert('JavaScript with Prototype.');"
392
+ def <<(javascript)
393
+ @lines << javascript
394
+ end
395
+
396
+ # Executes the content of the block after a delay of +seconds+. Example:
397
+ #
398
+ # # Generates:
399
+ # # setTimeout(function() {
400
+ # # ;
401
+ # # new Effect.Fade("notice",{});
402
+ # # }, 20000);
403
+ # page.delay(20) do
404
+ # page.visual_effect :fade, 'notice'
405
+ # end
406
+ def delay(seconds = 1)
407
+ record "setTimeout(function() {\n\n"
408
+ yield
409
+ record "}, #{(seconds * 1000).to_i})"
410
+ end
411
+
412
+ # Starts a script.aculo.us visual effect. See
413
+ # ActionView::Helpers::ScriptaculousHelper for more information.
414
+ def visual_effect(name, id = nil, options = {})
415
+ record jquery_ui_visual_effect(name, id, options)
416
+ end
417
+
418
+ SCRIPTACULOUS_EFFECTS = {
419
+ :appear => {:method => 'fade', :mode => 'show'},
420
+ :blind_down => {:method => 'blind', :mode => 'show', :options => {:direction => 'vertical'}},
421
+ :blind_up => {:method => 'blind', :mode => 'hide', :options => {:direction => 'vertical'}},
422
+ :blind_right => {:method => 'blind', :mode => 'show', :options => {:direction => 'horizontal'}},
423
+ :blind_left => {:method => 'blind', :mode => 'hide', :options => {:direction => 'horizontal'}},
424
+ :bounce_in => {:method => 'bounce', :mode => 'show', :options => {:direction => 'up'}},
425
+ :bounce_out => {:method => 'bounce', :mode => 'hide', :options => {:direction => 'up'}},
426
+ :drop_in => {:method => 'drop', :mode => 'show', :options => {:direction => 'up'}},
427
+ :drop_out => {:method => 'drop', :mode => 'hide', :options => {:direction => 'down'}},
428
+ :fade => {:method => 'fade', :mode => 'hide'},
429
+ :fold_in => {:method => 'fold', :mode => 'hide'},
430
+ :fold_out => {:method => 'fold', :mode => 'show'},
431
+ :grow => {:method => 'scale', :mode => 'show'},
432
+ :shrink => {:method => 'scale', :mode => 'hide'},
433
+ :slide_down => {:method => 'slide', :mode => 'show', :options => {:direction => 'up'}},
434
+ :slide_up => {:method => 'slide', :mode => 'hide', :options => {:direction => 'up'}},
435
+ :slide_right => {:method => 'slide', :mode => 'show', :options => {:direction => 'left'}},
436
+ :slide_left => {:method => 'slide', :mode => 'hide', :options => {:direction => 'left'}},
437
+ :squish => {:method => 'scale', :mode => 'hide', :options => {:origin => "['top','left']"}},
438
+ :switch_on => {:method => 'clip', :mode => 'show', :options => {:direction => 'vertical'}},
439
+ :switch_off => {:method => 'clip', :mode => 'hide', :options => {:direction => 'vertical'}},
440
+ :toggle_appear => {:method => 'fade', :mode => 'toggle'},
441
+ :toggle_slide => {:method => 'slide', :mode => 'toggle', :options => {:direction => 'up'}},
442
+ :toggle_blind => {:method => 'blind', :mode => 'toggle', :options => {:direction => 'vertical'}},
443
+ }
444
+
445
+ # Returns a JavaScript snippet to be used on the Ajax callbacks for
446
+ # starting visual effects.
447
+ #
448
+ # If no +element_id+ is given, it assumes "element" which should be a local
449
+ # variable in the generated JavaScript execution context. This can be
450
+ # used for example with +drop_receiving_element+:
451
+ #
452
+ # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
453
+ #
454
+ # This would fade the element that was dropped on the drop receiving
455
+ # element.
456
+ #
457
+ # For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
458
+ # <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
459
+ # blinddown/blindup respectively.
460
+ #
461
+ # You can change the behaviour with various options, see
462
+ # http://script.aculo.us for more documentation.
463
+ def jquery_ui_visual_effect(name, element_id = false, js_options = {})
464
+ #element = element_id ? ActiveSupport::JSON.encode(jquery_id((JavaScriptVariableProxy === element_id) ? element_id.as_json : element_id)) : "this"
465
+ if element_id
466
+ element = if element_id =~ /\A<%=.*%>\z/ # if completely using erb
467
+ "\"##{element_id}\"" # USER BEWARE !
468
+ else
469
+ ActiveSupport::JSON.encode(jquery_id((JavaScriptVariableProxy === element_id) ? element_id.as_json : element_id))
470
+ end
471
+ else
472
+ element = "this"
473
+ end
474
+
475
+ if SCRIPTACULOUS_EFFECTS.has_key? name.to_sym
476
+ effect = SCRIPTACULOUS_EFFECTS[name.to_sym]
477
+ name = effect[:method]
478
+ mode = effect[:mode]
479
+ js_options = js_options.merge(effect[:options]) if effect[:options]
480
+ end
481
+
482
+ js_options[:queue] = if js_options[:queue].is_a?(Hash)
483
+ '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
484
+ elsif js_options[:queue]
485
+ "'#{js_options[:queue]}'"
486
+ end if js_options[:queue]
487
+
488
+ [:color, :direction, :startcolor, :endcolor].each do |option|
489
+ js_options[option] = "'#{js_options[option]}'" if js_options[option]
490
+ end
491
+
492
+ js_options[:duration] = (js_options[:duration] * 1000).to_i if js_options.has_key? :duration
493
+
494
+ #if ['fadeIn','fadeOut','fadeToggle'].include?(name)
495
+ # "$(\"#{jquery_id(element_id)}\").#{name}();"
496
+ #else
497
+ "#{JQUERY_VAR}(#{element}).#{mode || "effect"}(\"#{name}\",#{options_for_javascript(js_options)});"
498
+ #end
499
+
500
+ end
501
+
502
+ def arguments_for_call(arguments, block = nil)
503
+ arguments << block_to_function(block) if block
504
+ arguments.map { |argument| javascript_object_for(argument) }.join ', '
505
+ end
506
+
507
+ private
508
+ def options_for_javascript(options)
509
+ if options.empty?
510
+ '{}'
511
+ else
512
+ "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
513
+ end
514
+ end
515
+
516
+ def loop_on_multiple_args(method, ids)
517
+ record(ids.size>1 ?
518
+ "#{javascript_object_for(ids)}.each(#{method})" :
519
+ "#{method}(#{javascript_object_for(ids.first)})")
520
+ end
521
+
522
+ def page
523
+ self
524
+ end
525
+
526
+ def record(line)
527
+ line = "#{line.to_s.chomp.gsub(/\;\z/, '')};"
528
+ self << line
529
+ line
530
+ end
531
+
532
+ def render(*options)
533
+ with_formats(:html) do
534
+ case option = options.first
535
+ when Hash
536
+ @context.render(*options)
537
+ else
538
+ option.to_s
539
+ end
540
+ end
541
+ end
542
+
543
+ def with_formats(*args)
544
+ return yield unless @context
545
+
546
+ lookup = @context.lookup_context
547
+ begin
548
+ old_formats, lookup.formats = lookup.formats, args
549
+ yield
550
+ ensure
551
+ lookup.formats = old_formats
552
+ end
553
+ end
554
+
555
+ def javascript_object_for(object)
556
+ if object.is_a?(String) && object =~ /\A<%=.*%>\z/ # if completely using e
557
+ "\"#{object}\""
558
+ else
559
+ ::ActiveSupport::JSON.encode(object)
560
+ end
561
+ end
562
+
563
+ def block_to_function(block)
564
+ generator = self.class.new(@context, &block)
565
+ literal("function() { #{generator.to_s} }")
566
+ end
567
+
568
+ def method_missing(method, *arguments)
569
+ JavaScriptProxy.new(self, method.to_s.camelize)
570
+ end
571
+ end
572
+ end
573
+
574
+ class JavaScriptProxy < (Rails::VERSION::MAJOR >= 4) ? ::ActiveSupport::ProxyObject : ::ActiveSupport::BasicObject #:nodoc:
575
+
576
+ def initialize(generator, root = nil)
577
+ @generator = generator
578
+ @generator << root if root
579
+ end
580
+
581
+ def is_a?(klass)
582
+ klass == JavaScriptProxy
583
+ end
584
+
585
+ private
586
+ def method_missing(method, *arguments, &block)
587
+ if method.to_s =~ /(.*)=$/
588
+ assign($1, arguments.first)
589
+ else
590
+ call("#{method.to_s.camelize(:lower)}", *arguments, &block)
591
+ end
592
+ end
593
+
594
+ def call(function, *arguments, &block)
595
+ append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
596
+ self
597
+ end
598
+
599
+ def assign(variable, value)
600
+ append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
601
+ end
602
+
603
+ def function_chain
604
+ @function_chain ||= @generator.instance_variable_get(:@lines)
605
+ end
606
+
607
+ def append_to_function_chain!(call)
608
+ function_chain[-1].chomp!(';')
609
+ function_chain[-1] += ".#{call};"
610
+ end
611
+ end
612
+
613
+ class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
614
+ def initialize(generator, id)
615
+ id = id.sub(/<%=.*%>/,'').to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
616
+ #id = id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
617
+ @id = id
618
+ if id =~ /\A#?<%=.*%>\z/ # if completely using erb
619
+ super(generator, "#{::JQUERY_VAR}(\"#{id}\");") # USER BEWARE !
620
+ else
621
+ super(generator, "#{::JQUERY_VAR}(#{::ActiveSupport::JSON.encode(id)});")
622
+ end
623
+ end
624
+
625
+ # Allows access of element attributes through +attribute+. Examples:
626
+ #
627
+ # page['foo']['style'] # => $('foo').style;
628
+ # page['foo']['style']['color'] # => $('blank_slate').style.color;
629
+ # page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
630
+ # page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
631
+ def [](attribute)
632
+ append_to_function_chain!(attribute)
633
+ self
634
+ end
635
+
636
+ def []=(variable, value)
637
+ assign(variable, value)
638
+ end
639
+
640
+ def replace_html(*options_for_render)
641
+ call 'html', @generator.send(:render, *options_for_render)
642
+ end
643
+
644
+ def replace(*options_for_render)
645
+ call 'replaceWith', @generator.send(:render, *options_for_render)
646
+ end
647
+
648
+ def reload(options_for_replace = {})
649
+ replace(options_for_replace.merge({ :partial => @id.to_s.sub(/^#/,'') }))
650
+ end
651
+
652
+ def value()
653
+ call 'val()'
654
+ end
655
+
656
+ def value=(value)
657
+ call 'val', value
658
+ end
659
+ end
660
+
661
+ class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
662
+ def initialize(generator, variable)
663
+ @variable = JsonLiteral.new(variable)
664
+ @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
665
+ super(generator)
666
+ end
667
+
668
+ # The JSON Encoder calls this to check for the +to_json+ method
669
+ # Since it's a blank slate object, I suppose it responds to anything.
670
+ def respond_to?(*)
671
+ true
672
+ end
673
+
674
+ def as_json(options = nil)
675
+ @variable
676
+ end
677
+
678
+ private
679
+ def append_to_function_chain!(call)
680
+ @generator << @variable if @empty
681
+ @empty = false
682
+ super
683
+ end
684
+ end
685
+
686
+ class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
687
+ 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
688
+ ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
689
+ attr_reader :generator
690
+ delegate :arguments_for_call, :to => :generator
691
+
692
+ def initialize(generator, pattern)
693
+ super(generator, @pattern = pattern)
694
+ end
695
+
696
+ def each_slice(variable, number, &block)
697
+ if block
698
+ enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
699
+ else
700
+ add_variable_assignment!(variable)
701
+ append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
702
+ end
703
+ end
704
+
705
+ def grep(variable, pattern, &block)
706
+ enumerate :grep, :variable => variable, :return => true, :method_args => [JsonLiteral.new(pattern.inspect)], :yield_args => %w(value index), &block
707
+ end
708
+
709
+ def in_groups_of(variable, number, fill_with = nil)
710
+ arguments = [number]
711
+ arguments << fill_with unless fill_with.nil?
712
+ add_variable_assignment!(variable)
713
+ append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
714
+ end
715
+
716
+ def inject(variable, memo, &block)
717
+ enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
718
+ end
719
+
720
+ def pluck(variable, property)
721
+ add_variable_assignment!(variable)
722
+ append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
723
+ end
724
+
725
+ def zip(variable, *arguments, &block)
726
+ add_variable_assignment!(variable)
727
+ append_enumerable_function!("zip(#{arguments_for_call arguments}")
728
+ if block
729
+ function_chain[-1] += ", function(array) {"
730
+ yield JsonLiteral.new('array')
731
+ add_return_statement!
732
+ @generator << '});'
733
+ else
734
+ function_chain[-1] += ');'
735
+ end
736
+ end
737
+
738
+ private
739
+ def method_missing(method, *arguments, &block)
740
+ if ENUMERABLE_METHODS.include?(method)
741
+ returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
742
+ variable = arguments.first if returnable
743
+ enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
744
+ else
745
+ super
746
+ end
747
+ end
748
+
749
+ # Options
750
+ # * variable - name of the variable to set the result of the enumeration to
751
+ # * method_args - array of the javascript enumeration method args that occur before the function
752
+ # * yield_args - array of the javascript yield args
753
+ # * return - true if the enumeration should return the last statement
754
+ def enumerate(enumerable, options = {}, &block)
755
+ options[:method_args] ||= []
756
+ options[:yield_args] ||= []
757
+ yield_args = options[:yield_args] * ', '
758
+ method_args = arguments_for_call options[:method_args] # foo, bar, function
759
+ method_args << ', ' unless method_args.blank?
760
+ add_variable_assignment!(options[:variable]) if options[:variable]
761
+ append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
762
+ # only yield as many params as were passed in the block
763
+ yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
764
+ add_return_statement! if options[:return]
765
+ @generator << '});'
766
+ end
767
+
768
+ def add_variable_assignment!(variable)
769
+ function_chain.push("var #{variable} = #{function_chain.pop}")
770
+ end
771
+
772
+ def add_return_statement!
773
+ unless function_chain.last =~ /return/
774
+ function_chain.push("return #{function_chain.pop.chomp(';')};")
775
+ end
776
+ end
777
+
778
+ def append_enumerable_function!(call)
779
+ function_chain[-1].chomp!(';')
780
+ function_chain[-1] += ".#{call}"
781
+ end
782
+ end
783
+
784
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
785
+ def initialize(generator, pattern)
786
+ super(generator, "#{::JQUERY_VAR}(#{::ActiveSupport::JSON.encode(pattern)})")
787
+ end
788
+ end
789
+ end