0001"""
0002Scriptaculous Helpers
0003
0004Provides a set of helpers for calling Scriptaculous JavaScript 
0005functions, including those which create Ajax controls and visual effects.
0006
0007To be able to use these helpers, you must include the Prototype 
0008JavaScript framework and the Scriptaculous JavaScript library in your 
0009pages.
0010
0011The Scriptaculous helpers' behavior can be tweaked with various options.
0012See the documentation at http://script.aculo.us for more information on
0013using these helpers in your application.
0014"""
0015# Last synced with Rails copy at Revision 6057 on Feb 9th, 2007.
0016import simplejson as json
0017from prototype import *
0018from javascript import options_for_javascript, array_or_string_for_javascript
0019from prototype import AJAX_OPTIONS, javascript_tag
0020from tags import camelize
0021
0022def visual_effect(name, element_id=False, **js_options):
0023    """
0024    Returns a JavaScript snippet to be used on the Ajax callbacks for
0025    starting visual effects.
0026    
0027    Example::
0028    
0029        <% link_to_remote("Reload",  
0030                dict(url=url(action="reload"),
0031                     update="posts",
0032                     complete=visual_effect('highlight', "posts", duration=0.5))) %>
0033    
0034    If no element_id is given, it assumes "element" which should be a local
0035    variable in the generated JavaScript execution context. This can be 
0036    used for example with drop_receiving_element::
0037    
0038        <% drop_receving_element('some_element', loading=visual_effect('fade')) %>
0039    
0040    This would fade the element that was dropped on the drop receiving 
0041    element.
0042    
0043    For toggling visual effects, you can use ``toggle_appear``, ``toggle_slide``, and
0044    ``toggle_blind`` which will alternate between appear/fade, slidedown/slideup, and
0045    blinddown/blindup respectively.
0046    
0047    You can change the behaviour with various options, see
0048    http://script.aculo.us for more documentation.
0049    """
0050    element = (element_id and json.dumps(element_id)) or "element"
0051    if isinstance(js_options.get('queue'), dict):
0052        js_options['queue'] = '{%s}' %               ','.join(["%s:%s" % (k, (k == 'limit' and v) or "'%s'" % v)                             for k,v in js_options['queue'].iteritems()])
0055    elif js_options.has_key('queue'):
0056        js_options['queue'] = "'%s'" % js_options['queue']
0057
0058
0059    if 'toggle' in name:
0060        return "Effect.toggle(%s,'%s',%s);" % (element, name.replace('toggle_',''), options_for_javascript(js_options))
0061    return "new Effect.%s(%s,%s);" % (camelize(name), element, options_for_javascript(js_options))
0062
0063def parallel_effects(*effects, **js_options):
0064    """
0065    Wraps visual effects so they occur in parallel
0066    
0067    Example::
0068    
0069        parallel_effects(
0070            visual_effect('highlight, 'dom_id'),
0071            visual_effect('fade', 'dom_id'),
0072        )
0073    """
0074    str_effects = [e[:e.rindex(';')] for e in effects] # Remove trailing ';'
0075    return "new Effect.Parallel([%s], %s)" % (','.join(str_effects), options_for_javascript(js_options))
0076
0077def sortable_element(element_id, **options):
0078    """
0079    Makes the element with the DOM ID specified by ``element_id`` sortable.
0080    
0081    Uses drag-and-drop and makes an Ajax call whenever the sort order has
0082    changed. By default, the action called gets the serialized sortable
0083    element as parameters.
0084    
0085    Example::
0086
0087        <% sortable_element("my_list", url=url(action="order")) %>
0088    
0089    In the example, the server-side action gets a "my_list" array
0090    parameter containing the values of the ids of elements the
0091    sortable consists of, in the current order (like
0092    ``mylist=item1&mylist=item2``, where ``item1`` and ``item2`` are
0093    the ids of the ``<li>`` elements).
0094
0095    Note: For this to work, the sortable elements must have id
0096    attributes in the form ``string_identifier``. For example,
0097    ``item_1``. Only the identifier part of the id attribute will be
0098    serialized.
0099    
0100    You can change the behaviour with various options, see
0101    http://script.aculo.us for more documentation.
0102    """
0103    return javascript_tag(sortable_element_js(element_id, **options))
0104
0105def sortable_element_js(element_id, **options):
0106    if not isinstance(element_id, basestring):
0107        raise ValueError('Argument element_id must be a string')
0108    options.setdefault('with_', "Sortable.serialize('%s')" % element_id)
0109    options.setdefault('onUpdate', "function(){%s}" % remote_function(**options))
0110    for k in options.keys():
0111        if k in AJAX_OPTIONS: del options[k]
0112
0113    for option in ['tag', 'overlap', 'constraint', 'handle']:
0114        if options.has_key(option) and options[option]:
0115            options[option] = "'%s'" % options[option]
0116
0117    if options.has_key('containment'):
0118        options['containment'] = array_or_string_for_javascript(options['containment'])
0119    if options.has_key('only'):
0120        options['only'] = array_or_string_for_javascript(options['only'])
0121
0122    return "Sortable.create(%s, %s)" % (json.dumps(element_id), options_for_javascript(options))
0123
0124def draggable_element(element_id, **options):
0125    """
0126    Makes the element with the DOM ID specified by ``element_id`` draggable.
0127    
0128    Example::
0129
0130        <% draggable_element("my_image", revert=True)
0131    
0132    You can change the behaviour with various options, see
0133    http://script.aculo.us for more documentation.
0134    """
0135    return javascript_tag(draggable_element_js(element_id, **options))
0136
0137def draggable_element_js(element_id, **options):
0138    return "new Draggable(%s, %s)" % (json.dumps(element_id), options_for_javascript(options))
0139
0140def drop_receiving_element(element_id, **options):
0141    """
0142    Makes an element able to recieve dropped draggable elements
0143    
0144    Makes the element with the DOM ID specified by ``element_id`` receive
0145    dropped draggable elements (created by draggable_element) and make an
0146    AJAX call  By default, the action called gets the DOM ID of the element
0147    as parameter.
0148    
0149    Example::
0150    
0151        <% drop_receiving_element("my_cart", url=url_for(controller="cart", action="add" )) %>
0152    
0153    You can change the behaviour with various options, see
0154    http://script.aculo.us for more documentation.    
0155    """
0156    return javascript_tag(drop_receiving_element_js(element_id, **options))
0157
0158def drop_receiving_element_js(element_id, **options):
0159    options.setdefault('with_', "'id=' + encodeURIComponent(element.id)")
0160    options.setdefault('onDrop', "function(element){%s}" % remote_function(**options))
0161    for k in options.keys():
0162        if k in AJAX_OPTIONS: del options[k]
0163
0164    if options.has_key('accept'):
0165        options['accept'] = array_or_string_for_javascript(options['accept'])
0166    if options.has_key('hoverclass'):
0167        options['hoverclass'] = "'%s'" % options['hoverclass']
0168
0169    return "Droppables.add(%s, %s)" % (json.dumps(element_id), options_for_javascript(options))
0170
0171__all__ = ['visual_effect', 'parallel_effects', 'sortable_element', 'draggable_element', 'drop_receiving_element']