eita-jrails 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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