1 require 'action_view/helpers/javascript_helper'
5 # Provides a set of helpers for calling Scriptaculous JavaScript
6 # functions, including those which create Ajax controls and visual effects.
8 # To be able to use these helpers, you must include the Prototype
9 # JavaScript framework and the Scriptaculous JavaScript library in your
10 # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
11 # for more information on including the necessary JavaScript.
13 # The Scriptaculous helpers' behavior can be tweaked with various options.
14 # See the documentation at http://script.aculo.us for more information on
15 # using these helpers in your application.
16 module ScriptaculousHelper
17 unless const_defined
? :TOGGLE_EFFECTS
18 TOGGLE_EFFECTS
= [:toggle_appear, :toggle_slide, :toggle_blind]
21 # Returns a JavaScript snippet to be used on the Ajax callbacks for
22 # starting visual effects.
25 # <%= link_to_remote "Reload", :update => "posts",
26 # :url => { :action => "reload" },
27 # :complete => visual_effect(:highlight, "posts", :duration => 0.5)
29 # If no +element_id+ is given, it assumes "element" which should be a local
30 # variable in the generated JavaScript execution context. This can be
31 # used for example with +drop_receiving_element+:
33 # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
35 # This would fade the element that was dropped on the drop receiving
38 # For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
39 # <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
40 # blinddown/blindup respectively.
42 # You can change the behaviour with various options, see
43 # http://script.aculo.us for more documentation.
44 def visual_effect(name
, element_id
= false, js_options
= {})
45 element
= element_id
? element_id
.to_json
: "element"
47 js_options
[:queue] = if js_options
[:queue].is_a
?(Hash
)
48 '{' + js_options
[:queue].map
{|k
, v
| k
== :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
49 elsif js_options
[:queue]
50 "'#{js_options[:queue]}'"
51 end if js_options
[:queue]
53 [:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each
do |option
|
54 js_options
[option
] = "'#{js_options[option]}'" if js_options
[option
]
57 if TOGGLE_EFFECTS
.include? name
.to_sym
58 "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
60 "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
64 # Makes the element with the DOM ID specified by +element_id+ sortable
65 # by drag-and-drop and make an Ajax call whenever the sort order has
66 # changed. By default, the action called gets the serialized sortable
67 # element as parameters.
71 # <%= sortable_element("my_list", :url => { :action => "order" }) %>
73 # In the example, the action gets a "my_list" array parameter
74 # containing the values of the ids of elements the sortable consists
75 # of, in the current order.
77 # Important: For this to work, the sortable elements must have id
78 # attributes in the form "string_identifier". For example, "item_1". Only
79 # the identifier part of the id attribute will be serialized.
81 # Additional +options+ are:
83 # * <tt>:format</tt> - A regular expression to determine what to send as the
84 # serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
86 # * <tt>:constraint</tt> - Whether to constrain the dragging to either
87 # <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
89 # * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
90 # or <tt>:vertical</tt> direction.
92 # * <tt>:tag</tt> - Which children of the container element to treat as
93 # sortable (default is <tt>li</tt>).
95 # * <tt>:containment</tt> - Takes an element or array of elements to treat as
96 # potential drop targets (defaults to the original target element).
98 # * <tt>:only</tt> - A CSS class name or array of class names used to filter
99 # out child elements as candidates.
101 # * <tt>:scroll</tt> - Determines whether to scroll the list during drag
102 # operations if the list runs past the visual border.
104 # * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
105 # main sortable list. This means that you can create multi-layer lists,
106 # and not only sort items at the same level, but drag and sort items
109 # * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
110 # when an accepted Draggable is hovered over it.
112 # * <tt>:handle</tt> - Sets whether the element should only be draggable by an
113 # embedded handle. The value may be a string referencing a CSS class value
114 # (as of script.aculo.us V1.5). The first child/grandchild/etc. element
115 # found within the element that has this CSS class value will be used as
118 # * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
119 # the original in place until the clone is dropped (default is <tt>false</tt>).
121 # * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
122 # a Droppable, that can receive a Draggable (as according to the containment
123 # rules) as a child element when there are no more elements inside (default
124 # is <tt>false</tt>).
126 # * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
127 # dragging from one Sortable to another, the callback is called once on each
128 # Sortable. Gets the affected element as its parameter.
130 # * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
131 # changed in any way. When dragging from one Sortable to another, the callback
132 # is called once on each Sortable. Gets the container as its parameter.
134 # See http://script.aculo.us for more documentation.
135 def sortable_element(element_id
, options
= {})
136 javascript_tag(sortable_element_js(element_id
, options
).chop
!)
139 def sortable_element_js(element_id
, options
= {}) #:nodoc:
140 options
[:with] ||= "Sortable.serialize(#{element_id.to_json})"
141 options
[:onUpdate] ||= "function(){" + remote_function(options
) + "}"
142 options
.delete_if
{ |key
, value
| PrototypeHelper
::AJAX_OPTIONS.include?(key
) }
144 [:tag, :overlap, :constraint, :handle].each
do |option
|
145 options
[option
] = "'#{options[option]}'" if options
[option
]
148 options
[:containment] = array_or_string_for_javascript(options
[:containment]) if options
[:containment]
149 options
[:only] = array_or_string_for_javascript(options
[:only]) if options
[:only]
151 %(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
154 # Makes the element with the DOM ID specified by +element_id+ draggable.
157 # <%= draggable_element("my_image", :revert => true)
159 # You can change the behaviour with various options, see
160 # http://script.aculo.us for more documentation.
161 def draggable_element(element_id, options = {})
162 javascript_tag(draggable_element_js(element_id, options).chop!)
165 def draggable_element_js(element_id, options = {}) #:nodoc:
166 %(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
169 # Makes the element with the DOM ID specified by +element_id+ receive
170 # dropped draggable elements (created by +draggable_element+).
171 # and make an AJAX call. By default, the action called gets the DOM ID
172 # of the element as parameter.
175 # <%= drop_receiving_element("my_cart", :url =>
176 # { :controller => "cart", :action => "add" }) %>
178 # You can change the behaviour with various options, see
179 # http://script.aculo.us for more documentation.
181 # Some of these +options+ include:
182 # * <tt>:accept</tt> - Set this to a string or an array of strings describing the
183 # allowable CSS classes that the +draggable_element+ must have in order
184 # to be accepted by this +drop_receiving_element+.
186 # * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
188 # :confirm => "Are you sure you want to do this?"
190 # * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
191 # this additional CSS class when an accepted +draggable_element+ is
194 # * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
195 # this element. Override this callback with a JavaScript expression to
196 # change the default drop behaviour. Example:
198 # :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
200 # This callback gets three parameters: The Draggable element, the Droppable
201 # element and the Event object. You can extract additional information about
202 # the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
204 # * <tt>:with</tt> - A JavaScript expression specifying the parameters for
205 # the XMLHttpRequest. Any expressions should return a valid URL query string.
206 def drop_receiving_element(element_id, options = {})
207 javascript_tag(drop_receiving_element_js(element_id, options).chop!)
210 def drop_receiving_element_js(element_id, options = {}) #:nodoc:
211 options[:with] ||= "'id=' + encodeURIComponent(element.id)"
212 options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
213 options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
215 options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
216 options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
218 # Confirmation happens during the onDrop callback, so it can be removed from the options
219 options.delete(:confirm) if options[:confirm]
221 %(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)