root/galaxy-central/eggs/WebHelpers-0.2-py2.6.egg/webhelpers/rails/prototype.py @ 3

リビジョン 3, 16.1 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1"""
2Prototype Helpers
3
4Provides a set of helpers for calling Prototype JavaScript functions,
5including functionality to call remote methods using
6`Ajax <http://www.adaptivepath.com/publications/essays/archives/000385.php>`_.
7This means that you can call actions in your controllers without
8reloading the page, but still update certain parts of it using
9injections into the DOM. The common use case is having a form that adds
10a new element to a list without reloading the page.
11
12To be able to use these helpers, you must include the Prototype
13JavaScript framework in your pages.
14
15See `link_to_remote <module-railshelpers.helpers.javascript.html#link_to_function>`_
16for documentation of options common to all Ajax helpers.
17
18See also `Scriptaculous <module-railshelpers.helpers.scriptaculous.html>`_ for
19helpers which work with the Scriptaculous controls and visual effects library.
20"""
21# Last synced with Rails copy at Revision 4235 on Aug 19th, 2006.
22
23import sys
24if sys.version < '2.4':
25    from sets import ImmutableSet as frozenset
26
27from javascript import *
28from javascript import options_for_javascript
29from form_tag import form
30from tags import tag, camelize
31from urls import get_url
32
33CALLBACKS = frozenset(['uninitialized','loading','loaded',
34                       'interactive','complete','failure','success'] + [str(x) for x in range(100,599)])
35AJAX_OPTIONS = frozenset(['before','after','condition','url',
36                          'asynchronous','method','insertion','position',
37                          'form','with','update','script'] + list(CALLBACKS))
38
39def link_to_remote(name, options={}, **html_options):
40    """
41    Links to a remote function
42   
43    Returns a link to a remote action defined ``dict(url=url())``
44    (using the url() format) that's called in the background using
45    XMLHttpRequest. The result of that request can then be inserted into a
46    DOM object whose id can be specified with the ``update`` keyword.
47    Usually, the result would be a partial prepared by the controller with
48    either render_partial or render_partial_collection.
49   
50    Any keywords given after the second dict argument are considered html options
51    and assigned as html attributes/values for the element.
52   
53    Example::
54   
55        link_to_remote("Delete this post", dict(update="posts",
56                       url=url(action="destroy", id=post.id)))
57   
58    You can also specify a dict for ``update`` to allow for easy redirection
59    of output to an other DOM element if a server-side error occurs:
60   
61    Example::
62
63        link_to_remote("Delete this post",
64                dict(url=url(action="destroy", id=post.id),
65                     update=dict(success="posts", failure="error")))
66   
67    Optionally, you can use the ``position`` parameter to influence how the
68    target DOM element is updated. It must be one of 'before', 'top', 'bottom',
69    or 'after'.
70   
71    By default, these remote requests are processed asynchronous during
72    which various JavaScript callbacks can be triggered (for progress
73    indicators and the likes). All callbacks get access to the
74    ``request`` object, which holds the underlying XMLHttpRequest.
75   
76    To access the server response, use ``request.responseText``, to
77    find out the HTTP status, use ``request.status``.
78   
79    Example::
80
81        link_to_remote(word,
82                dict(url=url(action="undo", n=word_counter),
83                     complete="undoRequestCompleted(request)"))
84   
85    The callbacks that may be specified are (in order):
86   
87    ``loading``
88        Called when the remote document is being loaded with data by the browser.
89    ``loaded``
90        Called when the browser has finished loading the remote document.
91    ``interactive``
92        Called when the user can interact with the remote document, even
93        though it has not finished loading.
94    ``success``
95        Called when the XMLHttpRequest is completed, and the HTTP status
96        code is in the 2XX range.
97    ``failure``
98        Called when the XMLHttpRequest is completed, and the HTTP status code is
99        not in the 2XX range.
100    ``complete``
101        Called when the XMLHttpRequest is complete (fires after success/failure
102        if they are present).
103                       
104    You can further refine ``success`` and ``failure`` by
105    adding additional callbacks for specific status codes.
106   
107    Example::
108   
109        link_to_remote(word,
110                dict(url=url(action="action"),
111                     404="alert('Not found...? Wrong URL...?')",
112                     failure="alert('HTTP Error ' + request.status + '!')"))
113   
114    A status code callback overrides the success/failure handlers if
115    present.
116   
117    If you for some reason or another need synchronous processing (that'll
118    block the browser while the request is happening), you can specify
119    ``type='synchronous'``.
120   
121    You can customize further browser side call logic by passing in
122    JavaScript code snippets via some optional parameters. In their order
123    of use these are:
124   
125    ``confirm``
126        Adds confirmation dialog.
127    ``condition``
128        Perform remote request conditionally by this expression. Use this to
129        describe browser-side conditions when request should not be initiated.
130    ``before``
131        Called before request is initiated.
132    ``after``
133        Called immediately after request was initiated and before ``loading``.
134    ``submit``
135        Specifies the DOM element ID that's used as the parent of the form
136        elements. By default this is the current form, but it could just as
137        well be the ID of a table row or any other DOM element.   
138    """
139    return link_to_function(name, remote_function(**options), **html_options)
140
141def periodically_call_remote(**options):
142    """
143    Periodically calls a remote function
144   
145    Periodically calls the specified ``url`` every ``frequency`` seconds
146    (default is 10). Usually used to update a specified div ``update``
147    with the results of the remote call. The options for specifying the
148    target with ``url`` and defining callbacks is the same as `link_to_remote <#link_to_remote>`_.   
149    """
150    frequency = options.get('frequency') or 10
151    code = "new PeriodicalExecuter(function() {%s}, %s)" % (remote_function(**options), frequency)
152    return javascript_tag(code)
153
154def form_remote_tag(**options):
155    """
156    Create a form tag using a remote function to submit the request
157   
158    Returns a form tag that will submit using XMLHttpRequest in the
159    background instead of the regular reloading POST arrangement. Even
160    though it's using JavaScript to serialize the form elements, the form
161    submission will work just like a regular submission as viewed by the
162    receiving side. The options for specifying the target with ``url``
163    and defining callbacks is the same as `link_to_remote <#link_to_remote>`_.
164   
165    A "fall-through" target for browsers that doesn't do JavaScript can be
166    specified with the ``action/method`` options on ``html``.
167   
168    Example::
169
170        form_remote_tag(html=dict(action=url(
171                                    controller="some", action="place")))
172   
173    By default the fall-through action is the same as the one specified in
174    the ``url`` (and the default method is ``post``).
175    """
176    options['form'] = True
177    if 'html' not in options: options['html'] = {}
178    options['html']['onsubmit'] = "%s; return false;" % remote_function(**options)
179    action = options['html'].get('action', get_url(options['url']))
180    options['html']['method'] = options['html'].get('method', 'post')
181   
182    return form(action, **options['html'])
183
184def submit_to_remote(name, value, **options):
185    """
186    A submit button that submits via an XMLHttpRequest call
187   
188    Returns a button input tag that will submit form using XMLHttpRequest
189    in the background instead of regular reloading POST arrangement.
190    Keyword args are the same as in ``form_remote_tag``.   
191    """
192    options['with'] = options.get('form') or 'Form.serialize(this.form)'
193   
194    options['html'] = options.get('html') or {}
195    options['html']['type'] = 'button'
196    options['html']['onclick'] = "%s; return false;" % remote_function(**options)
197    options['html']['name_'] = name
198    options['html']['value'] = str(value)
199   
200    return tag("input", open=False, **options['html'])
201
202def update_element_function(element_id, **options):
203    """
204    Returns a JavaScript function (or expression) that'll update a DOM
205    element.
206   
207    ``content``
208        The content to use for updating.
209    ``action``
210        Valid options are 'update' (assumed by default), 'empty', 'remove'
211    ``position``
212        If the ``action`` is 'update', you can optionally specify one of the
213        following positions: 'before', 'top', 'bottom', 'after'.
214   
215    Example::
216   
217        <% javascript_tag(update_element_function("products",
218            position='bottom', content="<p>New product!</p>")) %>
219   
220    This method can also be used in combination with remote method call
221    where the result is evaluated afterwards to cause multiple updates on
222    a page. Example::
223   
224        # Calling view
225        <% form_remote_tag(url=url(action="buy"),
226                complete=evaluate_remote_response()) %>
227            all the inputs here...
228   
229        # Controller action
230        def buy(self, **params):
231            c.product = Product.find(1)
232            m.subexec('/buy.myt')
233   
234        # Returning view
235        <% update_element_function(
236                "cart", action='update', position='bottom',
237                content="<p>New Product: %s</p>" % c.product.name) %>
238        <% update_element_function("status", binding='binding',
239                content="You've bought a new product!") %>
240    """
241    content = escape_javascript(options.get('content', ''))
242    opval = options.get('action', 'update')
243    if opval == 'update':
244        if options.get('position'):
245            jsf = "new Insertion.%s('%s','%s')" % (camelize(options['position']), element_id, content)
246        else:
247            jsf = "$('%s').innerHTML = '%s'" % (element_id, content)
248    elif opval == 'empty':
249        jsf = "$('%s').innerHTML = ''" % element_id
250    elif opval == 'remove':
251        jsf = "Element.remove('%s')" % element_id
252    else:
253        raise "Invalid action, choose one of update, remove, or empty"
254   
255    jsf += ";\n"
256    if options.get('binding'):
257        return jsf + options['binding']
258    else:
259        return jsf
260
261def evaluate_remote_response():
262    """
263    Returns a Javascript function that evals a request response
264   
265    Returns 'eval(request.responseText)' which is the JavaScript function
266    that ``form_remote_tag`` can call in *complete* to evaluate a multiple
267    update return document using ``update_element_function`` calls.   
268    """
269    return "eval(request.responseText)"
270
271def remote_function(**options):
272    """
273    Returns the JavaScript needed for a remote function.
274   
275    Takes the same arguments as `link_to_remote <#link_to_remote>`_.
276   
277    Example::
278   
279        <select id="options" onchange="<% remote_function(update="options",
280                url=url(action='update_options')) %>">
281            <option value="0">Hello</option>
282            <option value="1">World</option>
283        </select>   
284    """
285    javascript_options = options_for_ajax(options)
286   
287    update = ''
288    if options.get('update') and isinstance(options['update'], dict):
289        update = []
290        if options['update'].has_key('success'):
291            update.append("success:'%s'" % options['update']['success'])
292        if options['update'].has_key('failure'):
293            update.append("failure:'%s'" % options['update']['failure'])
294        update = '{' + ','.join(update) + '}'
295    elif options.get('update'):
296        update += "'%s'" % options['update']
297   
298    function = "new Ajax.Request("
299    if update: function = "new Ajax.Updater(%s, " % update
300   
301    function += "'%s'" % get_url(options['url'])
302    function += ", %s)" % javascript_options
303   
304    if options.get('before'):
305        function = "%s; %s" % (options['before'], function)
306    if options.get('after'):
307        function = "%s; %s" % (function, options['after'])
308    if options.get('condition'):
309        function = "if (%s) { %s; }" % (options['condition'], function)
310    if options.get('confirm'):
311        function = "if (confirm('%s')) { %s; }" % (escape_javascript(options['confirm']), function)
312   
313    return function
314
315def observe_field(field_id, **options):
316    """
317    Observes the field with the DOM ID specified by ``field_id`` and makes
318    an Ajax call when its contents have changed.
319   
320    Required keyword args are:
321   
322    ``url``
323        ``url()``-style options for the action to call when the
324        field has changed.
325   
326    Additional keyword args are:
327   
328    ``frequency``
329        The frequency (in seconds) at which changes to this field will be
330        detected. Not setting this option at all or to a value equal to or
331        less than zero will use event based observation instead of time
332        based observation.
333    ``update``
334        Specifies the DOM ID of the element whose innerHTML should be
335        updated with the XMLHttpRequest response text.
336    ``with``
337        A JavaScript expression specifying the parameters for the
338        XMLHttpRequest. This defaults to 'value', which in the evaluated
339        context refers to the new field value.
340    ``on``
341        Specifies which event handler to observe. By default, it's set to
342        "changed" for text fields and areas and "click" for radio buttons
343        and checkboxes. With this, you can specify it instead to be "blur"
344        or "focus" or any other event.
345   
346    Additionally, you may specify any of the options documented in
347    `link_to_remote <#link_to_remote>`_.
348    """
349    if options.get('frequency') > 0:
350        return build_observer('Form.Element.Observer', field_id, **options)
351    else:
352        return build_observer('Form.Element.EventObserver', field_id, **options)
353
354def observe_form(form_id, **options):
355    """
356    Like `observe_field <#observe_field>`_, but operates on an entire form
357    identified by the DOM ID ``form_id``.
358   
359    Keyword args are the same as observe_field, except the default value of
360    the ``with`` keyword evaluates to the serialized (request string) value
361    of the form.
362    """
363    if options.get('frequency'):
364        return build_observer('Form.Observer', form_id, **options)
365    else:
366        return build_observer('Form.EventObserver', form_id, **options)
367
368def options_for_ajax(options):
369    js_options = build_callbacks(options)
370   
371    js_options['asynchronous'] = str(options.get('type') != 'synchronous').lower()
372    if options.get('method'):
373        if isinstance(options['method'], str) and options['method'].startswith("'"):
374            js_options['method'] = options['method']
375        else:
376            js_options['method'] = "'%s'" % options['method']
377    if options.get('position'):
378        js_options['insertion'] = "Insertion.%s" % camelize(options['position'])
379    js_options['evalScripts'] = str(options.get('script') is None or options['script']).lower()
380   
381    if options.get('form'):
382        js_options['parameters'] = 'Form.serialize(this)'
383    elif options.get('submit'):
384        js_options['parameters'] = "Form.serialize('%s')" % options['submit']
385    elif options.get('with'):
386        js_options['parameters'] = options['with']
387   
388    return options_for_javascript(js_options)
389
390def build_observer(cls, name, **options):
391    if options.get('update') is True:
392        options['with'] = options.get('with', 'value')
393    callback = remote_function(**options)
394    javascript = "new %s('%s', " % (cls, name)
395    if options.get('frequency'):
396        javascript += "%s, " % options['frequency']
397    javascript += "function(element, value) {%s})" % callback
398    return javascript_tag(javascript)
399
400def build_callbacks(options):
401    callbacks = {}
402    for callback, code in options.iteritems():
403        if callback in CALLBACKS:
404            name = 'on' + callback.title()
405            callbacks[name] = "function(request){%s}" % code
406    return callbacks
407
408__all__ = ['link_to_remote', 'periodically_call_remote', 'form_remote_tag', 'submit_to_remote', 'update_element_function',
409           'evaluate_remote_response', 'remote_function', 'observe_field', 'observe_form']
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。