eita-jrails 0.9.0 → 0.9.1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -0
  3. data/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  4. data/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png +0 -0
  5. data/assets/images/jquery-ui/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  6. data/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  7. data/assets/images/jquery-ui/ui-bg_glass_75_dadada_1x400.png +0 -0
  8. data/assets/images/jquery-ui/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  9. data/assets/images/jquery-ui/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  10. data/assets/images/jquery-ui/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  11. data/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
  12. data/assets/images/jquery-ui/ui-icons_2e83ff_256x240.png +0 -0
  13. data/assets/images/jquery-ui/ui-icons_454545_256x240.png +0 -0
  14. data/assets/images/jquery-ui/ui-icons_888888_256x240.png +0 -0
  15. data/assets/images/jquery-ui/ui-icons_cd0a0a_256x240.png +0 -0
  16. data/assets/javascripts/jquery/jquery-ui-i18n.js +1379 -0
  17. data/assets/javascripts/jquery/jquery-ui-i18n.min.js +33 -0
  18. data/assets/javascripts/jrails/jrails.js +0 -12
  19. data/assets/stylesheets/smoothness/jquery-ui.css +573 -0
  20. data/lib/action_view/helpers/generator.rb +336 -0
  21. data/lib/action_view/helpers/jquery_helper.rb +558 -0
  22. data/lib/action_view/helpers/jquery_ui_helper.rb +165 -0
  23. data/lib/action_view/helpers/proxies.rb +187 -0
  24. data/lib/action_view/helpers/scriptaculous_helper.rb +263 -0
  25. data/lib/action_view/template/handlers/rjs.rb +14 -0
  26. data/lib/jrails.rb +3 -7
  27. data/lib/jrails/engine.rb +26 -0
  28. data/lib/jrails/javascript_helper.rb +97 -0
  29. data/lib/jrails/on_load_action_controller.rb +2 -0
  30. data/lib/jrails/on_load_action_view.rb +26 -0
  31. data/lib/jrails/renderers.rb +12 -0
  32. data/lib/jrails/rendering.rb +13 -0
  33. data/lib/jrails/selector_assertions.rb +211 -0
  34. metadata +37 -12
  35. data/README.rdoc +0 -27
  36. data/init.rb +0 -3
  37. data/install.rb +0 -2
  38. data/lib/jrails/jquery_selector_assertions.rb +0 -60
@@ -0,0 +1,165 @@
1
+ module ActionView
2
+ module Helpers
3
+
4
+ module JqueryUiHelper
5
+
6
+ JQUERY_VAR = ::JRails::JQUERY_VAR
7
+
8
+ SCRIPTACULOUS_EFFECTS = {
9
+ appear: {method: 'fadeIn'},
10
+ blind_down: {method: 'blind', mode: 'show', options: {direction: 'vertical'}},
11
+ blind_up: {method: 'blind', mode: 'hide', options: {direction: 'vertical'}},
12
+ blind_right: {method: 'blind', mode: 'show', options: {direction: 'horizontal'}},
13
+ blind_left: {method: 'blind', mode: 'hide', options: {direction: 'horizontal'}},
14
+ bounce_in: {method: 'bounce', mode: 'show', options: {direction: 'up'}},
15
+ bounce_out: {method: 'bounce', mode: 'hide', options: {direction: 'up'}},
16
+ drop_in: {method: 'drop', mode: 'show', options: {direction: 'up'}},
17
+ drop_out: {method: 'drop', mode: 'hide', options: {direction: 'down'}},
18
+ fade: {method: 'fadeOut'},
19
+ fold_in: {method: 'fold', mode: 'hide'},
20
+ fold_out: {method: 'fold', mode: 'show'},
21
+ grow: {method: 'scale', mode: 'show'},
22
+ shrink: {method: 'scale', mode: 'hide'},
23
+ slide_down: {method: 'slide', mode: 'show', options: {direction: 'up'}},
24
+ slide_up: {method: 'slide', mode: 'hide', options: {direction: 'up'}},
25
+ slide_right: {method: 'slide', mode: 'show', options: {direction: 'left'}},
26
+ slide_left: {method: 'slide', mode: 'hide', options: {direction: 'left'}},
27
+ squish: {method: 'scale', mode: 'hide', options: {origin: "['top','left']"}},
28
+ switch_on: {method: 'clip', mode: 'show', options: {direction: 'vertical'}},
29
+ switch_off: {method: 'clip', mode: 'hide', options: {direction: 'vertical'}},
30
+ toggle_appear: {method: 'fadeToggle'},
31
+ toggle_slide: {method: 'slide', mode: 'toggle', options: {direction: 'up'}},
32
+ toggle_blind: {method: 'blind', mode: 'toggle', options: {direction: 'vertical'}},
33
+ }
34
+
35
+ def visual_effect(name, element_id = false, js_options = {})
36
+ if SCRIPTACULOUS_EFFECTS.has_key? name.to_sym
37
+ effect = SCRIPTACULOUS_EFFECTS[name.to_sym]
38
+ name = effect[:method]
39
+ mode = effect[:mode]
40
+ js_options = js_options.merge(effect[:options]) if effect[:options]
41
+ end
42
+
43
+ [:color, :direction, :startcolor, :endcolor].each do |option|
44
+ js_options[option] = "'#{js_options[option]}'" if js_options[option]
45
+ end
46
+
47
+ if js_options.has_key? :duration
48
+ speed = js_options.delete :duration
49
+ speed = (speed * 1000).to_i unless speed.nil?
50
+ else
51
+ speed = js_options.delete :speed
52
+ end
53
+
54
+ if ['fadeIn','fadeOut','fadeToggle'].include?(name)
55
+ # 090905 - Jake - changed ' to \" so it passes assert_select_rjs with an id
56
+ javascript = "#{JQUERY_VAR}(\"#{jquery_id(element_id)}\").#{name}("
57
+ javascript << "#{speed}" unless speed.nil?
58
+ javascript << ");"
59
+ else
60
+ # 090905 - Jake - changed ' to \" so it passes "assert_select_rjs :effect, ID"
61
+ javascript = "#{JQUERY_VAR}(\"#{jquery_id(element_id)}\").#{mode || 'effect'}('#{name}'"
62
+ javascript << ",#{options_for_javascript(js_options)}" unless speed.nil? && js_options.empty?
63
+ javascript << ",#{speed}" unless speed.nil?
64
+ javascript << ");"
65
+ end
66
+
67
+ end
68
+
69
+ def drop_receiving_element(element_id, options = {})
70
+ javascript_tag(drop_receiving_element_js(element_id, options).chop!)
71
+ end
72
+
73
+ def draggable_element(element_id, options = {})
74
+ javascript_tag(draggable_element_js(element_id, options).chop!)
75
+ end
76
+
77
+ def sortable_element(element_id, options = {})
78
+ javascript_tag(sortable_element_js(element_id, options).chop!)
79
+ end
80
+
81
+ def sortable_element_js(element_id, options = {}) #:nodoc:
82
+ #convert similar attributes
83
+ options[:handle] = ".#{options[:handle]}" if options[:handle]
84
+ if options[:tag] || options[:only]
85
+ options[:items] = "> "
86
+ options[:items] << options.delete(:tag) if options[:tag]
87
+ options[:items] << ".#{options.delete(:only)}" if options[:only]
88
+ end
89
+ options[:connectWith] = options.delete(:containment).map {|x| "##{x}"} if options[:containment]
90
+ options[:containment] = options.delete(:container) if options[:container]
91
+ options[:dropOnEmpty] = false unless options[:dropOnEmpty]
92
+ options[:helper] = "'clone'" if options[:ghosting] == true
93
+ options[:axis] = case options.delete(:constraint)
94
+ when "vertical", :vertical
95
+ "y"
96
+ when "horizontal", :horizontal
97
+ "x"
98
+ when false
99
+ nil
100
+ when nil
101
+ "y"
102
+ end
103
+ options.delete(:axis) if options[:axis].nil?
104
+ options.delete(:overlap)
105
+ options.delete(:ghosting)
106
+
107
+ if options[:onUpdate] || options[:url]
108
+ if options[:format]
109
+ options[:with] ||= "#{JQUERY_VAR}(this).sortable('serialize',{key:'#{element_id}[]', expression:#{options[:format]}})"
110
+ options.delete(:format)
111
+ else
112
+ options[:with] ||= "#{JQUERY_VAR}(this).sortable('serialize',{key:'#{element_id}[]'})"
113
+ end
114
+
115
+ options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
116
+ end
117
+
118
+ options.delete_if { |key, value| JqueryHelper::AJAX_OPTIONS.include?(key) }
119
+ options[:update] = options.delete(:onUpdate) if options[:onUpdate]
120
+
121
+ [:axis, :cancel, :containment, :cursor, :handle, :tolerance, :items, :placeholder].each do |option|
122
+ options[option] = "'#{options[option]}'" if options[option]
123
+ end
124
+
125
+ options[:connectWith] = array_or_string_for_javascript(options[:connectWith]) if options[:connectWith]
126
+
127
+ %(#{JQUERY_VAR}('#{jquery_id(element_id)}').sortable(#{options_for_javascript(options)});)
128
+ end
129
+
130
+ def draggable_element_js(element_id, options = {})
131
+ %(#{JQUERY_VAR}("#{jquery_id(element_id)}").draggable(#{options_for_javascript(options)});)
132
+ end
133
+
134
+ def drop_receiving_element_js(element_id, options = {})
135
+ #convert similar options
136
+ options[:hoverClass] = options.delete(:hoverclass) if options[:hoverclass]
137
+ options[:drop] = options.delete(:onDrop) if options[:onDrop]
138
+
139
+ if options[:drop] || options[:url]
140
+ options[:with] ||= "'id=' + encodeURIComponent(#{JQUERY_VAR}(ui.draggable).attr('id'))"
141
+ options[:drop] ||= "function(ev, ui){" + remote_function(options) + "}"
142
+ end
143
+
144
+ options.delete_if { |key, value| JqueryHelper::AJAX_OPTIONS.include?(key) }
145
+
146
+ options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
147
+ [:activeClass, :hoverClass, :tolerance].each do |option|
148
+ options[option] = "'#{options[option]}'" if options[option]
149
+ end
150
+
151
+ %(#{JQUERY_VAR}('#{jquery_id(element_id)}').droppable(#{options_for_javascript(options)});)
152
+ end
153
+
154
+ def array_or_string_for_javascript(option)
155
+ if option.kind_of?(Array)
156
+ "['#{option.join('\',\'')}']"
157
+ elsif !option.nil?
158
+ "'#{option}'"
159
+ end
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,187 @@
1
+ require 'action_view/helpers/proxies'
2
+
3
+ module ActionView
4
+ module Helpers
5
+
6
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
7
+ # copied from rails 3.0.20
8
+ class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
9
+
10
+ def initialize(generator, root = nil)
11
+ @generator = generator
12
+ @generator << root if root
13
+ end
14
+
15
+ def is_a?(klass)
16
+ klass == JavaScriptProxy
17
+ end
18
+
19
+ private
20
+ def method_missing(method, *arguments, &block)
21
+ if method.to_s =~ /(.*)=$/
22
+ assign($1, arguments.first)
23
+ else
24
+ call("#{method.to_s.camelize(:lower)}", *arguments, &block)
25
+ end
26
+ end
27
+
28
+ def call(function, *arguments, &block)
29
+ append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
30
+ self
31
+ end
32
+
33
+ def assign(variable, value)
34
+ append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
35
+ end
36
+
37
+ def function_chain
38
+ @function_chain ||= @generator.instance_variable_get(:@lines)
39
+ end
40
+
41
+ def append_to_function_chain!(call)
42
+ function_chain[-1].chomp!(';')
43
+ function_chain[-1] += ".#{call};"
44
+ end
45
+ end
46
+
47
+ # copied from rails 3.0.20
48
+ class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
49
+ 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
50
+ ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
51
+ attr_reader :generator
52
+ delegate :arguments_for_call, :to => :generator
53
+
54
+ def initialize(generator, pattern)
55
+ super(generator, @pattern = pattern)
56
+ end
57
+
58
+ def each_slice(variable, number, &block)
59
+ if block
60
+ enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
61
+ else
62
+ add_variable_assignment!(variable)
63
+ append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
64
+ end
65
+ end
66
+
67
+ def grep(variable, pattern, &block)
68
+ enumerate :grep, :variable => variable, :return => true, :method_args => [::ActiveSupport::JSON::Variable.new(pattern.inspect)], :yield_args => %w(value index), &block
69
+ end
70
+
71
+ def in_groups_of(variable, number, fill_with = nil)
72
+ arguments = [number]
73
+ arguments << fill_with unless fill_with.nil?
74
+ add_variable_assignment!(variable)
75
+ append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
76
+ end
77
+
78
+ def inject(variable, memo, &block)
79
+ enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
80
+ end
81
+
82
+ def pluck(variable, property)
83
+ add_variable_assignment!(variable)
84
+ append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
85
+ end
86
+
87
+ def zip(variable, *arguments, &block)
88
+ add_variable_assignment!(variable)
89
+ append_enumerable_function!("zip(#{arguments_for_call arguments}")
90
+ if block
91
+ function_chain[-1] += ", function(array) {"
92
+ yield ::ActiveSupport::JSON::Variable.new('array')
93
+ add_return_statement!
94
+ @generator << '});'
95
+ else
96
+ function_chain[-1] += ');'
97
+ end
98
+ end
99
+
100
+ private
101
+ def method_missing(method, *arguments, &block)
102
+ if ENUMERABLE_METHODS.include?(method)
103
+ returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
104
+ enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ # Options
111
+ # * variable - name of the variable to set the result of the enumeration to
112
+ # * method_args - array of the javascript enumeration method args that occur before the function
113
+ # * yield_args - array of the javascript yield args
114
+ # * return - true if the enumeration should return the last statement
115
+ def enumerate(enumerable, options = {}, &block)
116
+ options[:method_args] ||= []
117
+ options[:yield_args] ||= []
118
+ yield_args = options[:yield_args] * ', '
119
+ method_args = arguments_for_call options[:method_args] # foo, bar, function
120
+ method_args << ', ' unless method_args.blank?
121
+ add_variable_assignment!(options[:variable]) if options[:variable]
122
+ append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
123
+ # only yield as many params as were passed in the block
124
+ yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
125
+ add_return_statement! if options[:return]
126
+ @generator << '});'
127
+ end
128
+
129
+ def add_variable_assignment!(variable)
130
+ function_chain.push("var #{variable} = #{function_chain.pop}")
131
+ end
132
+
133
+ def add_return_statement!
134
+ unless function_chain.last =~ /return/
135
+ function_chain.push("return #{function_chain.pop.chomp(';')};")
136
+ end
137
+ end
138
+
139
+ def append_enumerable_function!(call)
140
+ function_chain[-1].chomp!(';')
141
+ function_chain[-1] += ".#{call}"
142
+ end
143
+ end
144
+
145
+ class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
146
+
147
+ JQUERY_VAR = ::JRails::JQUERY_VAR
148
+
149
+ def initialize(generator, id)
150
+ id = id.to_s.count('#.*,>+~:[/ ') == 0 ? "##{id}" : id
151
+ @id = id
152
+ super(generator, "#{JQUERY_VAR}(\"#{id}\")")
153
+ end
154
+
155
+ def replace_html(*options_for_render)
156
+ call 'html', @generator.send(:render, *options_for_render)
157
+ end
158
+
159
+ def replace(*options_for_render)
160
+ call 'replaceWith', @generator.send(:render, *options_for_render)
161
+ end
162
+
163
+ def reload(options_for_replace={})
164
+ replace(options_for_replace.merge({ :partial => @id.to_s.sub(/^#/,'') }))
165
+ end
166
+
167
+ def value()
168
+ call 'val()'
169
+ end
170
+
171
+ def value=(value)
172
+ call 'val', value
173
+ end
174
+
175
+ end
176
+
177
+ class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
178
+
179
+ JQUERY_VAR = ::JRails::JQUERY_VAR
180
+
181
+ def initialize(generator, pattern)
182
+ super(generator, "#{JQUERY_VAR}(#{pattern.to_json})")
183
+ end
184
+ end
185
+
186
+ end
187
+ end
@@ -0,0 +1,263 @@
1
+ require 'action_view/helpers/javascript_helper'
2
+ require 'active_support/json'
3
+
4
+ module ActionView
5
+ # = Action View Scriptaculous Helpers
6
+ module Helpers
7
+ # Provides a set of helpers for calling Scriptaculous[http://script.aculo.us/]
8
+ # JavaScript functions, including those which create Ajax controls and visual
9
+ # effects.
10
+ #
11
+ # To be able to use these helpers, you must include the Prototype
12
+ # JavaScript framework and the Scriptaculous JavaScript library in your
13
+ # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
14
+ # for more information on including the necessary JavaScript.
15
+ #
16
+ # The Scriptaculous helpers' behavior can be tweaked with various options.
17
+ #
18
+ # See the documentation at http://script.aculo.us for more information on
19
+ # using these helpers in your application.
20
+ module ScriptaculousHelper
21
+ TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
22
+
23
+ # Returns a JavaScript snippet to be used on the Ajax callbacks for
24
+ # starting visual effects.
25
+ #
26
+ # If no +element_id+ is given, it assumes "element" which should be a local
27
+ # variable in the generated JavaScript execution context. This can be
28
+ # used for example with +drop_receiving_element+:
29
+ #
30
+ # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
31
+ #
32
+ # This would fade the element that was dropped on the drop receiving
33
+ # element.
34
+ #
35
+ # For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
36
+ # <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
37
+ # blinddown/blindup respectively.
38
+ #
39
+ # You can change the behaviour with various options, see
40
+ # http://script.aculo.us for more documentation.
41
+ def visual_effect(name, element_id = false, js_options = {})
42
+ element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
43
+
44
+ js_options[:queue] = if js_options[:queue].is_a?(Hash)
45
+ '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
46
+ elsif js_options[:queue]
47
+ "'#{js_options[:queue]}'"
48
+ end if js_options[:queue]
49
+
50
+ [:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
51
+ js_options[option] = "'#{js_options[option]}'" if js_options[option]
52
+ end
53
+
54
+ if TOGGLE_EFFECTS.include? name.to_sym
55
+ "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
56
+ else
57
+ "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
58
+ end
59
+ end
60
+
61
+ # Makes the element with the DOM ID specified by +element_id+ sortable
62
+ # by drag-and-drop and make an Ajax call whenever the sort order has
63
+ # changed. By default, the action called gets the serialized sortable
64
+ # element as parameters.
65
+ #
66
+ # Example:
67
+ #
68
+ # <%= sortable_element("my_list", :url => { :action => "order" }) %>
69
+ #
70
+ # In the example, the action gets a "my_list" array parameter
71
+ # containing the values of the ids of elements the sortable consists
72
+ # of, in the current order.
73
+ #
74
+ # Important: For this to work, the sortable elements must have id
75
+ # attributes in the form "string_identifier". For example, "item_1". Only
76
+ # the identifier part of the id attribute will be serialized.
77
+ #
78
+ # Additional +options+ are:
79
+ #
80
+ # * <tt>:format</tt> - A regular expression to determine what to send as the
81
+ # serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
82
+ #
83
+ # * <tt>:constraint</tt> - Whether to constrain the dragging to either
84
+ # <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
85
+ #
86
+ # * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
87
+ # or <tt>:vertical</tt> direction.
88
+ #
89
+ # * <tt>:tag</tt> - Which children of the container element to treat as
90
+ # sortable (default is <tt>li</tt>).
91
+ #
92
+ # * <tt>:containment</tt> - Takes an element or array of elements to treat as
93
+ # potential drop targets (defaults to the original target element).
94
+ #
95
+ # * <tt>:only</tt> - A CSS class name or array of class names used to filter
96
+ # out child elements as candidates.
97
+ #
98
+ # * <tt>:scroll</tt> - Determines whether to scroll the list during drag
99
+ # operations if the list runs past the visual border.
100
+ #
101
+ # * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
102
+ # main sortable list. This means that you can create multi-layer lists,
103
+ # and not only sort items at the same level, but drag and sort items
104
+ # between levels.
105
+ #
106
+ # * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
107
+ # when an accepted Draggable is hovered over it.
108
+ #
109
+ # * <tt>:handle</tt> - Sets whether the element should only be draggable by an
110
+ # embedded handle. The value may be a string referencing a CSS class value
111
+ # (as of script.aculo.us V1.5). The first child/grandchild/etc. element
112
+ # found within the element that has this CSS class value will be used as
113
+ # the handle.
114
+ #
115
+ # * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
116
+ # the original in place until the clone is dropped (default is <tt>false</tt>).
117
+ #
118
+ # * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
119
+ # a Droppable, that can receive a Draggable (as according to the containment
120
+ # rules) as a child element when there are no more elements inside (default
121
+ # is <tt>false</tt>).
122
+ #
123
+ # * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
124
+ # dragging from one Sortable to another, the callback is called once on each
125
+ # Sortable. Gets the affected element as its parameter.
126
+ #
127
+ # * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
128
+ # changed in any way. When dragging from one Sortable to another, the callback
129
+ # is called once on each Sortable. Gets the container as its parameter.
130
+ #
131
+ # See http://script.aculo.us for more documentation.
132
+ def sortable_element(element_id, options = {})
133
+ javascript_tag(sortable_element_js(element_id, options).chop!)
134
+ end
135
+
136
+ def sortable_element_js(element_id, options = {}) #:nodoc:
137
+ options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
138
+ options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
139
+ options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
140
+
141
+ [:tag, :overlap, :constraint, :handle].each do |option|
142
+ options[option] = "'#{options[option]}'" if options[option]
143
+ end
144
+
145
+ options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
146
+ options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
147
+
148
+ %(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
149
+ end
150
+
151
+ # Makes the element with the DOM ID specified by +element_id+ draggable.
152
+ #
153
+ # Example:
154
+ # <%= draggable_element("my_image", :revert => true)
155
+ #
156
+ # You can change the behaviour with various options, see
157
+ # http://script.aculo.us for more documentation.
158
+ def draggable_element(element_id, options = {})
159
+ javascript_tag(draggable_element_js(element_id, options).chop!)
160
+ end
161
+
162
+ def draggable_element_js(element_id, options = {}) #:nodoc:
163
+ %(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
164
+ end
165
+
166
+ # Makes the element with the DOM ID specified by +element_id+ receive
167
+ # dropped draggable elements (created by +draggable_element+).
168
+ # and make an AJAX call. By default, the action called gets the DOM ID
169
+ # of the element as parameter.
170
+ #
171
+ # Example:
172
+ # <%= drop_receiving_element("my_cart", :url =>
173
+ # { :controller => "cart", :action => "add" }) %>
174
+ #
175
+ # You can change the behaviour with various options, see
176
+ # http://script.aculo.us for more documentation.
177
+ #
178
+ # Some of these +options+ include:
179
+ # * <tt>:accept</tt> - Set this to a string or an array of strings describing the
180
+ # allowable CSS classes that the +draggable_element+ must have in order
181
+ # to be accepted by this +drop_receiving_element+.
182
+ #
183
+ # * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
184
+ #
185
+ # :confirm => "Are you sure you want to do this?"
186
+ #
187
+ # * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
188
+ # this additional CSS class when an accepted +draggable_element+ is
189
+ # hovered over it.
190
+ #
191
+ # * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
192
+ # this element. Override this callback with a JavaScript expression to
193
+ # change the default drop behaviour. Example:
194
+ #
195
+ # :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
196
+ #
197
+ # This callback gets three parameters: The Draggable element, the Droppable
198
+ # element and the Event object. You can extract additional information about
199
+ # the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
200
+ #
201
+ # * <tt>:with</tt> - A JavaScript expression specifying the parameters for
202
+ # the XMLHttpRequest. Any expressions should return a valid URL query string.
203
+ def drop_receiving_element(element_id, options = {})
204
+ javascript_tag(drop_receiving_element_js(element_id, options).chop!)
205
+ end
206
+
207
+ def drop_receiving_element_js(element_id, options = {}) #:nodoc:
208
+ options[:with] ||= "'id=' + encodeURIComponent(element.id)"
209
+ options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
210
+ options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
211
+
212
+ options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
213
+ options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
214
+
215
+ # Confirmation happens during the onDrop callback, so it can be removed from the options
216
+ options.delete(:confirm) if options[:confirm]
217
+
218
+ %(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
219
+ end
220
+
221
+ protected
222
+ def array_or_string_for_javascript(option)
223
+ if option.kind_of?(Array)
224
+ "['#{option.join('\',\'')}']"
225
+ elsif !option.nil?
226
+ "'#{option}'"
227
+ end
228
+ end
229
+ end
230
+
231
+ module PrototypeHelper
232
+ class JavaScriptGenerator
233
+ module GeneratorMethods
234
+ # Starts a script.aculo.us visual effect. See
235
+ # ActionView::Helpers::ScriptaculousHelper for more information.
236
+ def visual_effect(name, id = nil, options = {})
237
+ record @context.send(:visual_effect, name, id, options)
238
+ end
239
+
240
+ # Creates a script.aculo.us sortable element. Useful
241
+ # to recreate sortable elements after items get added
242
+ # or deleted.
243
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
244
+ def sortable(id, options = {})
245
+ record @context.send(:sortable_element_js, id, options)
246
+ end
247
+
248
+ # Creates a script.aculo.us draggable element.
249
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
250
+ def draggable(id, options = {})
251
+ record @context.send(:draggable_element_js, id, options)
252
+ end
253
+
254
+ # Creates a script.aculo.us drop receiving element.
255
+ # See ActionView::Helpers::ScriptaculousHelper for more information.
256
+ def drop_receiving(id, options = {})
257
+ record @context.send(:drop_receiving_element_js, id, options)
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end