jrails 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ #
2
+ # jQuery Selector Assertions (modifications to the prototype/scriptaculous assertions)
3
+ #
4
+ # From http://pastie.org/303776
5
+ #
6
+ # 1. Make sure to use '#' prefix when referring to element IDs in assert_select_rjs(),
7
+ # like this:
8
+ # assert_select_rjs :replace_html, '#someid'
9
+ # instead of prototype convention:
10
+ # assert_select_rjs :replace_html, 'someid'
11
+ #
12
+ # We monkey-patch some RJS-matching constants for assert_select_rjs to work
13
+ # with jQuery-based code as opposed to Prototype's:
14
+ #
15
+ module ActionController
16
+ module Assertions
17
+ module SelectorAssertions
18
+ silence_warnings do
19
+ RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
20
+ # RJS_ANY_ID = "\"([^\"])*\""
21
+ # better match with single or double quoted ids
22
+ RJS_ANY_ID = "[\"']([^\"])*[\"']"
23
+
24
+ RJS_STATEMENTS = {
25
+ :chained_replace => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.replaceWith\\(#{RJS_PATTERN_HTML}\\)",
26
+ :chained_replace_html => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.updateWith\\(#{RJS_PATTERN_HTML}\\)",
27
+ :replace_html => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.html\\(#{RJS_PATTERN_HTML}\\)",
28
+ :replace => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.replaceWith\\(#{RJS_PATTERN_HTML}\\)",
29
+ :insert_top => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.prepend\\(#{RJS_PATTERN_HTML}\\)",
30
+ :insert_bottom => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.append\\(#{RJS_PATTERN_HTML}\\)",
31
+ :effect => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.effect\\(",
32
+ :highlight => "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.effect\\('highlight'"
33
+
34
+ =begin TODO:
35
+
36
+ I've never used the chained_* so I don't know if they work.
37
+
38
+ I couldn't seem to get assert_select_rjs to actually match the single quoted ids
39
+ which are created by some of the effects like ...
40
+ ... jQuery('#item_1559').effect('highlight',{},1000);
41
+ so I modified jrails/lib/jrails.rb line 337
42
+ ... javascript = "#{JQUERY_VAR}('#{jquery_id(element_id)}').#{mode || 'effect'}('#{name}'"
43
+ to
44
+ ... javascript = "#{JQUERY_VAR}(\"#{jquery_id(element_id)}\").#{mode || 'effect'}('#{name}'"
45
+ so it writes double quotes like most of the others. This change should probably be
46
+ done to the others, but as I don't use them so haven't tested them.
47
+
48
+ My other option seemed to require modifying rails' selector_assertions.rb line 427
49
+ ... id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
50
+ which forces the expectation that the id is double quoted. If I changed it to
51
+ ... statement.gsub(RJS_ANY_ID, "[\"']{1}#{id}[\"']{1}")
52
+ I believe that it would work as the logic seemed to work in some testing.
53
+ I have not actually tried to modify rails, as this file doesn't seem to
54
+ actually be in the git repository.
55
+
56
+
57
+ jrails now uses a nonconflict option so $ is jQuery. I put both in the pattern in case it gets changed.
58
+
59
+ :insert_after => "",
60
+ :insert_before => "",
61
+ =end
62
+
63
+ }
64
+
65
+ [:remove, :show, :hide, :toggle, :reset ].each do |action|
66
+ RJS_STATEMENTS[action] = "\(jQuery|$\)\\(#{RJS_ANY_ID}\\)\\.#{action}\\(\\)"
67
+ end
68
+
69
+ # TODO:
70
+ #RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}):
71
+ #{RJS_PATTERN_HTML} \\}\\)"
72
+
73
+ RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
74
+ RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,423 @@
1
+ module ActionView
2
+ module Helpers
3
+
4
+ module JavaScriptHelper
5
+
6
+ # This function can be used to render rjs inline
7
+ #
8
+ # <%= javascript_function do |page|
9
+ # page.replace_html :list, :partial => 'list', :object => @list
10
+ # end %>
11
+ #
12
+ def javascript_function(*args, &block)
13
+ html_options = args.extract_options!
14
+ function = args[0] || ''
15
+
16
+ html_options.symbolize_keys!
17
+ function = update_page(&block) if block_given?
18
+ javascript_tag(function)
19
+ end
20
+
21
+ def jquery_id(id)
22
+ id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
23
+ end
24
+
25
+ def jquery_ids(ids)
26
+ Array(ids).map{|id| jquery_id(id)}.join(',')
27
+ end
28
+
29
+ end
30
+
31
+ module PrototypeHelper
32
+
33
+ USE_PROTECTION = const_defined?(:DISABLE_JQUERY_FORGERY_PROTECTION) ? !DISABLE_JQUERY_FORGERY_PROTECTION : true
34
+
35
+ unless const_defined? :JQUERY_VAR
36
+ JQUERY_VAR = 'jQuery'
37
+ end
38
+
39
+ unless const_defined? :JQCALLBACKS
40
+ JQCALLBACKS = Set.new([ :beforeSend, :complete, :error, :success ] + (100..599).to_a)
41
+ #instance_eval { remove_const :AJAX_OPTIONS }
42
+ remove_const(:AJAX_OPTIONS) if const_defined?(:AJAX_OPTIONS)
43
+ AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
44
+ :asynchronous, :method, :insertion, :position,
45
+ :form, :with, :update, :script ]).merge(JQCALLBACKS)
46
+ end
47
+
48
+ def periodically_call_remote(options = {})
49
+ frequency = options[:frequency] || 10 # every ten seconds by default
50
+ code = "setInterval(function() {#{remote_function(options)}}, #{frequency} * 1000)"
51
+ javascript_tag(code)
52
+ end
53
+
54
+ def remote_function(options)
55
+ javascript_options = options_for_ajax(options)
56
+
57
+ update = ''
58
+ if options[:update] && options[:update].is_a?(Hash)
59
+ update = []
60
+ update << "success:'#{options[:update][:success]}'" if options[:update][:success]
61
+ update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
62
+ update = '{' + update.join(',') + '}'
63
+ elsif options[:update]
64
+ update << "'#{options[:update]}'"
65
+ end
66
+
67
+ function = "#{JQUERY_VAR}.ajax(#{javascript_options})"
68
+
69
+ function = "#{options[:before]}; #{function}" if options[:before]
70
+ function = "#{function}; #{options[:after]}" if options[:after]
71
+ function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
72
+ function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
73
+ return function
74
+ end
75
+
76
+ class JavaScriptGenerator
77
+ module GeneratorMethods
78
+
79
+ def insert_html(position, id, *options_for_render)
80
+ insertion = position.to_s.downcase
81
+ insertion = 'append' if insertion == 'bottom'
82
+ insertion = 'prepend' if insertion == 'top'
83
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").#{insertion}", render(*options_for_render)
84
+ end
85
+
86
+ def replace_html(id, *options_for_render)
87
+ insert_html(:html, id, *options_for_render)
88
+ end
89
+
90
+ def replace(id, *options_for_render)
91
+ call "#{JQUERY_VAR}(\"#{jquery_id(id)}\").replaceWith", render(*options_for_render)
92
+ end
93
+
94
+ def remove(*ids)
95
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").remove"
96
+ end
97
+
98
+ def show(*ids)
99
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").show"
100
+ end
101
+
102
+ def hide(*ids)
103
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").hide"
104
+ end
105
+
106
+ def toggle(*ids)
107
+ call "#{JQUERY_VAR}(\"#{jquery_ids(ids)}\").toggle"
108
+ end
109
+
110
+ def jquery_id(id)
111
+ id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
112
+ end
113
+
114
+ def jquery_ids(ids)
115
+ Array(ids).map{|id| jquery_id(id)}.join(',')
116
+ end
117
+
118
+ end
119
+ end
120
+
121
+ protected
122
+ def options_for_ajax(options)
123
+ js_options = build_callbacks(options)
124
+
125
+ url_options = options[:url]
126
+ url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
127
+ js_options['url'] = "'#{url_for(url_options)}'"
128
+ js_options['async'] = false if options[:type] == :synchronous
129
+ js_options['type'] = options[:method] ? method_option_to_s(options[:method]) : ( options[:form] ? "'post'" : nil )
130
+ js_options['dataType'] = options[:datatype] ? "'#{options[:datatype]}'" : (options[:update] ? nil : "'script'")
131
+
132
+ if options[:form]
133
+ js_options['data'] = "#{JQUERY_VAR}.param(#{JQUERY_VAR}(this).serializeArray())"
134
+ elsif options[:submit]
135
+ js_options['data'] = "#{JQUERY_VAR}(\"##{options[:submit]} :input\").serialize()"
136
+ elsif options[:with]
137
+ js_options['data'] = options[:with].gsub("Form.serialize(this.form)","#{JQUERY_VAR}.param(#{JQUERY_VAR}(this.form).serializeArray())")
138
+ end
139
+
140
+ js_options['type'] ||= "'post'"
141
+ if options[:method]
142
+ if method_option_to_s(options[:method]) == "'put'" || method_option_to_s(options[:method]) == "'delete'"
143
+ js_options['type'] = "'post'"
144
+ if js_options['data']
145
+ js_options['data'] << " + '&"
146
+ else
147
+ js_options['data'] = "'"
148
+ end
149
+ js_options['data'] << "_method=#{options[:method]}'"
150
+ end
151
+ end
152
+
153
+ if USE_PROTECTION && respond_to?('protect_against_forgery?') && protect_against_forgery?
154
+ if js_options['data']
155
+ js_options['data'] << " + '&"
156
+ else
157
+ js_options['data'] = "'"
158
+ end
159
+ js_options['data'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
160
+ end
161
+ js_options['data'] = "''" if js_options['type'] == "'post'" && js_options['data'].nil?
162
+ options_for_javascript(js_options.reject {|key, value| value.nil?})
163
+ end
164
+
165
+ def build_update_for_success(html_id, insertion=nil)
166
+ insertion = build_insertion(insertion)
167
+ "#{JQUERY_VAR}('#{jquery_id(html_id)}').#{insertion}(request);"
168
+ end
169
+
170
+ def build_update_for_error(html_id, insertion=nil)
171
+ insertion = build_insertion(insertion)
172
+ "#{JQUERY_VAR}('#{jquery_id(html_id)}').#{insertion}(request.responseText);"
173
+ end
174
+
175
+ def build_insertion(insertion)
176
+ insertion = insertion ? insertion.to_s.downcase : 'html'
177
+ insertion = 'append' if insertion == 'bottom'
178
+ insertion = 'prepend' if insertion == 'top'
179
+ insertion
180
+ end
181
+
182
+ def build_observer(klass, name, options = {})
183
+ if options[:with] && (options[:with] !~ /[\{=(.]/)
184
+ options[:with] = "'#{options[:with]}=' + value"
185
+ else
186
+ options[:with] ||= 'value' unless options[:function]
187
+ end
188
+
189
+ callback = options[:function] || remote_function(options)
190
+ javascript = "#{JQUERY_VAR}('#{jquery_id(name)}').delayedObserver("
191
+ javascript << "#{options[:frequency] || 0}, "
192
+ javascript << "function(element, value) {"
193
+ javascript << "#{callback}}"
194
+ #javascript << ", '#{options[:on]}'" if options[:on]
195
+ javascript << ")"
196
+ javascript_tag(javascript)
197
+ end
198
+
199
+ def build_callbacks(options)
200
+ callbacks = {}
201
+ options[:beforeSend] = '';
202
+ [:uninitialized,:loading].each do |key|
203
+ options[:beforeSend] << (options[key].last == ';' ? options.delete(key) : options.delete(key) << ';') if options[key]
204
+ end
205
+ options.delete(:beforeSend) if options[:beforeSend].blank?
206
+ options[:complete] = options.delete(:loaded) if options[:loaded]
207
+ options[:error] = options.delete(:failure) if options[:failure]
208
+ if options[:update]
209
+ if options[:update].is_a?(Hash)
210
+ options[:update][:error] = options[:update].delete(:failure) if options[:update][:failure]
211
+ if options[:update][:success]
212
+ options[:success] = build_update_for_success(options[:update][:success], options[:position]) << (options[:success] ? options[:success] : '')
213
+ end
214
+ if options[:update][:error]
215
+ options[:error] = build_update_for_error(options[:update][:error], options[:position]) << (options[:error] ? options[:error] : '')
216
+ end
217
+ else
218
+ options[:success] = build_update_for_success(options[:update], options[:position]) << (options[:success] ? options[:success] : '')
219
+ end
220
+ end
221
+ options.each do |callback, code|
222
+ if JQCALLBACKS.include?(callback)
223
+ callbacks[callback] = "function(request){#{code}}"
224
+ end
225
+ end
226
+ callbacks
227
+ end
228
+
229
+ end
230
+
231
+ class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
232
+
233
+ unless const_defined? :JQUERY_VAR
234
+ JQUERY_VAR = PrototypeHelper::JQUERY_VAR
235
+ end
236
+
237
+ def initialize(generator, id)
238
+ id = id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
239
+ @id = id
240
+ super(generator, "#{JQUERY_VAR}(\"#{id}\")")
241
+ end
242
+
243
+ def replace_html(*options_for_render)
244
+ call 'html', @generator.send(:render, *options_for_render)
245
+ end
246
+
247
+ def replace(*options_for_render)
248
+ call 'replaceWith', @generator.send(:render, *options_for_render)
249
+ end
250
+
251
+ def reload(options_for_replace={})
252
+ replace(options_for_replace.merge({ :partial => @id.to_s.sub(/^#/,'') }))
253
+ end
254
+
255
+ def value()
256
+ call 'val()'
257
+ end
258
+
259
+ def value=(value)
260
+ call 'val', value
261
+ end
262
+
263
+ end
264
+
265
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
266
+
267
+ unless const_defined? :JQUERY_VAR
268
+ JQUERY_VAR = PrototypeHelper::JQUERY_VAR
269
+ end
270
+
271
+ def initialize(generator, pattern)
272
+ super(generator, "#{JQUERY_VAR}(#{pattern.to_json})")
273
+ end
274
+ end
275
+
276
+ module ScriptaculousHelper
277
+
278
+ unless const_defined? :JQUERY_VAR
279
+ JQUERY_VAR = PrototypeHelper::JQUERY_VAR
280
+ end
281
+
282
+ unless const_defined? :SCRIPTACULOUS_EFFECTS
283
+ SCRIPTACULOUS_EFFECTS = {
284
+ :appear => {:method => 'fadeIn'},
285
+ :blind_down => {:method => 'blind', :mode => 'show', :options => {:direction => 'vertical'}},
286
+ :blind_up => {:method => 'blind', :mode => 'hide', :options => {:direction => 'vertical'}},
287
+ :blind_right => {:method => 'blind', :mode => 'show', :options => {:direction => 'horizontal'}},
288
+ :blind_left => {:method => 'blind', :mode => 'hide', :options => {:direction => 'horizontal'}},
289
+ :bounce_in => {:method => 'bounce', :mode => 'show', :options => {:direction => 'up'}},
290
+ :bounce_out => {:method => 'bounce', :mode => 'hide', :options => {:direction => 'up'}},
291
+ :drop_in => {:method => 'drop', :mode => 'show', :options => {:direction => 'up'}},
292
+ :drop_out => {:method => 'drop', :mode => 'hide', :options => {:direction => 'down'}},
293
+ :fade => {:method => 'fadeOut'},
294
+ :fold_in => {:method => 'fold', :mode => 'hide'},
295
+ :fold_out => {:method => 'fold', :mode => 'show'},
296
+ :grow => {:method => 'scale', :mode => 'show'},
297
+ :shrink => {:method => 'scale', :mode => 'hide'},
298
+ :slide_down => {:method => 'slide', :mode => 'show', :options => {:direction => 'up'}},
299
+ :slide_up => {:method => 'slide', :mode => 'hide', :options => {:direction => 'up'}},
300
+ :slide_right => {:method => 'slide', :mode => 'show', :options => {:direction => 'left'}},
301
+ :slide_left => {:method => 'slide', :mode => 'hide', :options => {:direction => 'left'}},
302
+ :squish => {:method => 'scale', :mode => 'hide', :options => {:origin => "['top','left']"}},
303
+ :switch_on => {:method => 'clip', :mode => 'show', :options => {:direction => 'vertical'}},
304
+ :switch_off => {:method => 'clip', :mode => 'hide', :options => {:direction => 'vertical'}},
305
+ :toggle_appear => {:method => 'fadeToggle'},
306
+ :toggle_slide => {:method => 'slide', :mode => 'toggle', :options => {:direction => 'up'}},
307
+ :toggle_blind => {:method => 'blind', :mode => 'toggle', :options => {:direction => 'vertical'}},
308
+ }
309
+ end
310
+
311
+ def visual_effect(name, element_id = false, js_options = {})
312
+ element = element_id ? element_id : "this"
313
+
314
+ if SCRIPTACULOUS_EFFECTS.has_key? name.to_sym
315
+ effect = SCRIPTACULOUS_EFFECTS[name.to_sym]
316
+ name = effect[:method]
317
+ mode = effect[:mode]
318
+ js_options = js_options.merge(effect[:options]) if effect[:options]
319
+ end
320
+
321
+ [:color, :direction, :startcolor, :endcolor].each do |option|
322
+ js_options[option] = "'#{js_options[option]}'" if js_options[option]
323
+ end
324
+
325
+ if js_options.has_key? :duration
326
+ speed = js_options.delete :duration
327
+ speed = (speed * 1000).to_i unless speed.nil?
328
+ else
329
+ speed = js_options.delete :speed
330
+ end
331
+
332
+ if ['fadeIn','fadeOut','fadeToggle'].include?(name)
333
+ # 090905 - Jake - changed ' to \" so it passes assert_select_rjs with an id
334
+ javascript = "#{JQUERY_VAR}(\"#{jquery_id(element_id)}\").#{name}("
335
+ javascript << "#{speed}" unless speed.nil?
336
+ javascript << ");"
337
+ else
338
+ # 090905 - Jake - changed ' to \" so it passes "assert_select_rjs :effect, ID"
339
+ javascript = "#{JQUERY_VAR}(\"#{jquery_id(element_id)}\").#{mode || 'effect'}('#{name}'"
340
+ javascript << ",#{options_for_javascript(js_options)}" unless speed.nil? && js_options.empty?
341
+ javascript << ",#{speed}" unless speed.nil?
342
+ javascript << ");"
343
+ end
344
+
345
+ end
346
+
347
+ def sortable_element_js(element_id, options = {}) #:nodoc:
348
+ #convert similar attributes
349
+ options[:handle] = ".#{options[:handle]}" if options[:handle]
350
+ if options[:tag] || options[:only]
351
+ options[:items] = "> "
352
+ options[:items] << options.delete(:tag) if options[:tag]
353
+ options[:items] << ".#{options.delete(:only)}" if options[:only]
354
+ end
355
+ options[:connectWith] = options.delete(:containment).map {|x| "##{x}"} if options[:containment]
356
+ options[:containment] = options.delete(:container) if options[:container]
357
+ options[:dropOnEmpty] = false unless options[:dropOnEmpty]
358
+ options[:helper] = "'clone'" if options[:ghosting] == true
359
+ options[:axis] = case options.delete(:constraint)
360
+ when "vertical", :vertical
361
+ "y"
362
+ when "horizontal", :horizontal
363
+ "x"
364
+ when false
365
+ nil
366
+ when nil
367
+ "y"
368
+ end
369
+ options.delete(:axis) if options[:axis].nil?
370
+ options.delete(:overlap)
371
+ options.delete(:ghosting)
372
+
373
+ if options[:onUpdate] || options[:url]
374
+ if options[:format]
375
+ options[:with] ||= "#{JQUERY_VAR}(this).sortable('serialize',{key:'#{element_id}[]', expression:#{options[:format]}})"
376
+ options.delete(:format)
377
+ else
378
+ options[:with] ||= "#{JQUERY_VAR}(this).sortable('serialize',{key:'#{element_id}[]'})"
379
+ end
380
+
381
+ options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
382
+ end
383
+
384
+ options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
385
+ options[:update] = options.delete(:onUpdate) if options[:onUpdate]
386
+
387
+ [:axis, :cancel, :containment, :cursor, :handle, :tolerance, :items, :placeholder].each do |option|
388
+ options[option] = "'#{options[option]}'" if options[option]
389
+ end
390
+
391
+ options[:connectWith] = array_or_string_for_javascript(options[:connectWith]) if options[:connectWith]
392
+
393
+ %(#{JQUERY_VAR}('#{jquery_id(element_id)}').sortable(#{options_for_javascript(options)});)
394
+ end
395
+
396
+ def draggable_element_js(element_id, options = {})
397
+ %(#{JQUERY_VAR}("#{jquery_id(element_id)}").draggable(#{options_for_javascript(options)});)
398
+ end
399
+
400
+ def drop_receiving_element_js(element_id, options = {})
401
+ #convert similar options
402
+ options[:hoverClass] = options.delete(:hoverclass) if options[:hoverclass]
403
+ options[:drop] = options.delete(:onDrop) if options[:onDrop]
404
+
405
+ if options[:drop] || options[:url]
406
+ options[:with] ||= "'id=' + encodeURIComponent(#{JQUERY_VAR}(ui.draggable).attr('id'))"
407
+ options[:drop] ||= "function(ev, ui){" + remote_function(options) + "}"
408
+ end
409
+
410
+ options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
411
+
412
+ options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
413
+ [:activeClass, :hoverClass, :tolerance].each do |option|
414
+ options[option] = "'#{options[option]}'" if options[option]
415
+ end
416
+
417
+ %(#{JQUERY_VAR}('#{jquery_id(element_id)}').droppable(#{options_for_javascript(options)});)
418
+ end
419
+
420
+ end
421
+
422
+ end
423
+ end