Development

/branches/1.0/lib/helper/JavascriptHelper.php

You must first sign up to be able to contribute.

root/branches/1.0/lib/helper/JavascriptHelper.php

Revision 17386, 36.4 kB (checked in by dwhittle, 6 years ago)

[1.0] fixed confirm dialog does not work in ie6 (closes #4152)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  * (c) 2004 David Heinemeier Hansson
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 /**
13  * JavascriptHelper.
14  *
15  * @package    symfony
16  * @subpackage helper
17  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
18  * @author     John Christopher <john.christopher@symfony-project.com>
19  * @author     David Heinemeier Hansson
20  * @version    SVN: $Id$
21  */
22
23 /*
24  * Provides a set of helpers for calling JavaScript functions and, most importantly,
25  * to call remote methods using what has been labelled AJAX[http://www.adaptivepath.com/publications/essays/archives/000385.php].
26  * This means that you can call actions in your controllers without reloading the page,
27  * but still update certain parts of it using injections into the DOM.
28  * The common use case is having a form that adds a new element to a list without reloading the page.
29  *
30  * To be able to use the JavaScript helpers, you must include the Prototype JavaScript Framework
31  * and for some functions script.aculo.us (which both come with symfony) on your pages.
32  * Choose one of these options:
33  *
34  * * Use <tt><?php echo javascript_include_tag :defaults ?></tt> in the HEAD section of your page (recommended):
35  *   The function will return references to the JavaScript files created by the +rails+ command in your
36  *   <tt>public/javascripts</tt> directory. Using it is recommended as the browser can then cache the libraries
37  *   instead of fetching all the functions anew on every request.
38  * * Use <tt><?php echo javascript_include_tag 'prototype' ?></tt>: As above, but will only include the Prototype core library,
39  *   which means you are able to use all basic AJAX functionality. For the script.aculo.us-based JavaScript helpers,
40  *   like visual effects, autocompletion, drag and drop and so on, you should use the method described above.
41  * * Use <tt><?php echo define_javascript_functions ?></tt>: this will copy all the JavaScript support functions within a single
42  *   script block.
43  *
44  * For documentation on +javascript_include_tag+ see ActionView::Helpers::AssetTagHelper.
45  *
46  * If you're the visual type, there's an AJAX movie[http://www.rubyonrails.com/media/video/rails-ajax.mov] demonstrating
47  * the use of form_remote_tag.
48  */
49
50   function get_callbacks()
51   {
52     static $callbacks;
53     if (!$callbacks)
54     {
55       $callbacks = array_merge(array(
56         'uninitialized', 'loading', 'loaded', 'interactive', 'complete', 'failure', 'success'
57         ), range(100, 599));
58     }
59
60     return $callbacks;
61   }
62
63   function get_ajax_options()
64   {
65     static $ajax_options;
66     if (!$ajax_options)
67     {
68       $ajax_options = array_merge(array(
69         'before', 'after', 'condition', 'url', 'asynchronous', 'method',
70         'insertion', 'position', 'form', 'with', 'update', 'script'
71         ), get_callbacks());
72     }
73
74     return $ajax_options;
75   }
76
77   /**
78    * Returns a link that'll trigger a javascript function using the
79    * onclick handler and return false after the fact.
80    *
81    * Examples:
82    *   <?php echo link_to_function('Greeting', "alert('Hello world!')") ?>
83    *   <?php echo link_to_function(image_tag('delete'), "if confirm('Really?'){ do_delete(); }") ?>
84    */
85   function link_to_function($name, $function, $html_options = array())
86   {
87     $html_options = _parse_attributes($html_options);
88
89     $html_options['href'] = isset($html_options['href']) ? $html_options['href'] : '#';
90     $html_options['onclick'] = $function.'; return false;';
91
92     return content_tag('a', $name, $html_options);
93   }
94
95   /**
96    * Returns a button that'll trigger a javascript function using the
97    * onclick handler and return false after the fact.
98    *
99    * Examples:
100    *   <?php echo button_to_function('Greeting', "alert('Hello world!')") ?>
101    */
102   function button_to_function($name, $function, $html_options = array())
103   {
104     $html_options = _parse_attributes($html_options);
105
106     $html_options['onclick'] = $function.'; return false;';
107     $html_options['type']    = 'button';
108     $html_options['value']   = $name;
109
110     return tag('input', $html_options);
111   }
112  
113   /**
114    * Returns an html button to a remote action defined by 'url' (using the
115    * 'url_for()' format) that's called in the background using XMLHttpRequest.
116    *
117    * See link_to_remote() for details.
118    *
119    */
120   function button_to_remote($name, $options = array(), $html_options = array())
121   {
122     return button_to_function($name, remote_function($options), $html_options);
123   }
124
125   /**
126    * Returns a link to a remote action defined by 'url'
127    * (using the 'url_for()' format) that's called in the background using
128    * XMLHttpRequest. The result of that request can then be inserted into a
129    * DOM object whose id can be specified with 'update'.
130    * Usually, the result would be a partial prepared by the controller with
131    * either 'render_partial()'.
132    *
133    * Examples:
134    *  <?php echo link_to_remote('Delete this post'), array(
135    *    'update' => 'posts',
136    *    'url'    => 'destroy?id='.$post.id,
137    *  )) ?>
138    *  <?php echo link_to_remote(image_tag('refresh'), array(
139    *    'update' => 'emails',
140    *    'url'    => '@list_emails',
141    *  )) ?>
142    *
143    * You can also specify a hash for 'update' to allow for
144    * easy redirection of output to an other DOM element if a server-side error occurs:
145    *
146    * Example:
147    *  <?php echo link_to_remote('Delete this post', array(
148    *      'update' => array('success' => 'posts', 'failure' => 'error'),
149    *      'url'    => 'destroy?id='.$post.id,
150    *  )) ?>
151    *
152    * Optionally, you can use the 'position' parameter to influence
153    * how the target DOM element is updated. It must be one of
154    * 'before', 'top', 'bottom', or 'after'.
155    *
156    * By default, these remote requests are processed asynchronous during
157    * which various JavaScript callbacks can be triggered (for progress indicators and
158    * the likes). All callbacks get access to the 'request' object,
159    * which holds the underlying XMLHttpRequest.
160    *
161    * To access the server response, use 'request.responseText', to
162    * find out the HTTP status, use 'request.status'.
163    *
164    * Example:
165    *  <?php echo link_to_remote($word, array(
166    *    'url'      => '@undo?n='.$word_counter,
167    *    'complete' => 'undoRequestCompleted(request)'
168    *  )) ?>
169    *
170    * The callbacks that may be specified are (in order):
171    *
172    * 'loading'                 Called when the remote document is being
173    *                           loaded with data by the browser.
174    * 'loaded'                  Called when the browser has finished loading
175    *                           the remote document.
176    * 'interactive'             Called when the user can interact with the
177    *                           remote document, even though it has not
178    *                           finished loading.
179    * 'success'                 Called when the XMLHttpRequest is completed,
180    *                           and the HTTP status code is in the 2XX range.
181    * 'failure'                 Called when the XMLHttpRequest is completed,
182    *                           and the HTTP status code is not in the 2XX
183    *                           range.
184    * 'complete'                Called when the XMLHttpRequest is complete
185    *                           (fires after success/failure if they are present).,
186    *
187    * You can further refine 'success' and 'failure' by adding additional
188    * callbacks for specific status codes:
189    *
190    * Example:
191    *  <?php echo link_to_remote($word, array(
192    *       'url'     => '@rule',
193    *       '404'     => "alert('Not found...? Wrong URL...?')",
194    *       'failure' => "alert('HTTP Error ' + request.status + '!')",
195    *  )) ?>
196    *
197    * A status code callback overrides the success/failure handlers if present.
198    *
199    * If you for some reason or another need synchronous processing (that'll
200    * block the browser while the request is happening), you can specify
201    * 'type' => 'synchronous'.
202    *
203    * You can customize further browser side call logic by passing
204    * in JavaScript code snippets via some optional parameters. In
205    * their order of use these are:
206    *
207    * 'confirm'             Adds confirmation dialog.
208    * 'condition'           Perform remote request conditionally
209    *                       by this expression. Use this to
210    *                       describe browser-side conditions when
211    *                       request should not be initiated.
212    * 'before'              Called before request is initiated.
213    * 'after'               Called immediately after request was
214    *                       initiated and before 'loading'.
215    * 'submit'              Specifies the DOM element ID that's used
216    *                       as the parent of the form elements. By
217    *                       default this is the current form, but
218    *                       it could just as well be the ID of a
219    *                       table row or any other DOM element.
220    */
221   function link_to_remote($name, $options = array(), $html_options = array())
222   {
223     return link_to_function($name, remote_function($options), $html_options);
224   }
225
226   /**
227    * Periodically calls the specified url ('url') every 'frequency' seconds (default is 10).
228    * Usually used to update a specified div ('update') with the results of the remote call.
229    * The options for specifying the target with 'url' and defining callbacks is the same as 'link_to_remote()'.
230    */
231   function periodically_call_remote($options = array())
232   {
233     $frequency = isset($options['frequency']) ? $options['frequency'] : 10; // every ten seconds by default
234     $code = 'new PeriodicalExecuter(function() {'.remote_function($options).'}, '.$frequency.')';
235
236     return javascript_tag($code);
237   }
238
239   /**
240    * Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular
241    * reloading POST arrangement. Even though it's using JavaScript to serialize the form elements, the form submission
242    * will work just like a regular submission as viewed by the receiving side (all elements available in 'params').
243    * The options for specifying the target with 'url' and defining callbacks are the same as 'link_to_remote()'.
244    *
245    * A "fall-through" target for browsers that don't do JavaScript can be specified
246    * with the 'action'/'method' options on '$options_html'
247    *
248    * Example:
249    *  <?php echo form_remote_tag(array(
250    *    'url'      => '@tag_add',
251    *    'update'   => 'question_tags',
252    *    'loading'  => "Element.show('indicator'); \$('tag').value = ''",
253    *    'complete' => "Element.hide('indicator');".visual_effect('highlight', 'question_tags'),
254    *  )) ?>
255    *
256    * The hash passed as a second argument is equivalent to the options (2nd) argument in the form_tag() helper.
257    *
258    * By default the fall-through action is the same as the one specified in the 'url'
259    * (and the default method is 'post').
260    */
261   function form_remote_tag($options = array(), $options_html = array())
262   {
263     $options = _parse_attributes($options);
264     $options_html = _parse_attributes($options_html);
265
266     $options['form'] = true;
267
268     $options_html['onsubmit'] = remote_function($options).' return false;';
269     $options_html['action'] = isset($options_html['action']) ? $options_html['action'] : url_for($options['url']);
270     $options_html['method'] = isset($options_html['method']) ? $options_html['method'] : 'post';
271
272     return tag('form', $options_html, true);
273   }
274
275   /**
276    *  Returns a button input tag that will submit form using XMLHttpRequest in the background instead of regular
277    *  reloading POST arrangement. The '$options' argument is the same as in 'form_remote_tag()'.
278    */
279   function submit_to_remote($name, $value, $options = array(), $options_html = array())
280   {
281     $options = _parse_attributes($options);
282     $options_html = _parse_attributes($options_html);
283
284     if (!isset($options['with']))
285     {
286       $options['with'] = 'Form.serialize(this.form)';
287     }
288
289     $options_html['type'] = 'button';
290     $options_html['onclick'] = remote_function($options).' return false;';
291     $options_html['name'] = $name;
292     $options_html['value'] = $value;
293
294     return tag('input', $options_html, false);
295   }
296
297   /**
298    * Returns a Javascript function (or expression) that will update a DOM element '$element_id'
299    * according to the '$options' passed.
300    *
301    * Possible '$options' are:
302    * 'content'            The content to use for updating. Can be left out if using block, see example.
303    * 'action'             Valid options are 'update' (assumed by default), 'empty', 'remove'
304    * 'position'           If the 'action' is 'update', you can optionally specify one of the following positions:
305    *                      'before', 'top', 'bottom', 'after'.
306    *
307    * Example:
308    *   <?php echo javascript_tag(
309    *      update_element_function('products', array(
310    *            'position' => 'bottom',
311    *            'content'  => "<p>New product!</p>",
312    *      ))
313    *   ) ?>
314    *
315    *
316    * This method can also be used in combination with remote method call
317    * where the result is evaluated afterwards to cause multiple updates on a page.
318    *
319    * Example:
320    *
321    *  # Calling view
322    *  <?php echo form_remote_tag(array(
323    *      'url'      => '@buy',
324    *      'complete' => evaluate_remote_response()
325    *  )) ?>
326    *  all the inputs here...
327    *
328    *  # Target action
329    *  public function executeBuy()
330    *  {
331    *     $this->product = ProductPeer::retrieveByPk(1);
332    *  }
333    *
334    *  # Returning view
335    *  <php echo update_element_function('cart', array(
336    *      'action'   => 'update',
337    *      'position' => 'bottom',
338    *      'content'  => '<p>New Product: '.$product->getName().'</p>',
339    *  )) ?>
340    */
341   function update_element_function($element_id, $options = array())
342   {
343     sfContext::getInstance()->getResponse()->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
344
345     $content = escape_javascript(isset($options['content']) ? $options['content'] : '');
346
347     $value = isset($options['action']) ? $options['action'] : 'update';
348     switch ($value)
349     {
350       case 'update':
351         if (isset($options['position']) && $options['position'])
352         {
353           $javascript_function = "new Insertion.".sfInflector::camelize($options['position'])."('$element_id','$content')";
354         }
355         else
356         {
357           $javascript_function = "\$('$element_id').innerHTML = '$content'";
358         }
359         break;
360
361       case 'empty':
362         $javascript_function = "\$('$element_id').innerHTML = ''";
363         break;
364
365       case 'remove':
366         $javascript_function = "Element.remove('$element_id')";
367         break;
368
369       default:
370         throw new sfException('Invalid action, choose one of update, remove, empty');
371     }
372
373     $javascript_function .= ";\n";
374
375     return (isset($options['binding']) ? $javascript_function.$options['binding'] : $javascript_function);
376   }
377
378   /**
379    * Returns 'eval(request.responseText)', which is the Javascript function that
380    * 'form_remote_tag()' can call in 'complete' to evaluate a multiple update return document
381    * using 'update_element_function()' calls.
382    */
383   function evaluate_remote_response()
384   {
385     return 'eval(request.responseText)';
386   }
387
388   /**
389    * Returns the javascript needed for a remote function.
390    * Takes the same arguments as 'link_to_remote()'.
391    *
392    * Example:
393    *   <select id="options" onchange="<?php echo remote_function(array('update' => 'options', 'url' => '@update_options')) ?>">
394    *     <option value="0">Hello</option>
395    *     <option value="1">World</option>
396    *   </select>
397    */
398   function remote_function($options)
399   {
400     sfContext::getInstance()->getResponse()->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
401
402     $javascript_options = _options_for_ajax($options);
403
404     $update = '';
405     if (isset($options['update']) && is_array($options['update']))
406     {
407       $update = array();
408       if (isset($options['update']['success']))
409       {
410         $update[] = "success:'".$options['update']['success']."'";
411       }
412       if (isset($options['update']['failure']))
413       {
414         $update[] = "failure:'".$options['update']['failure']."'";
415       }
416       $update = '{'.join(',', $update).'}';
417     }
418     else if (isset($options['update']))
419     {
420       $update .= "'".$options['update']."'";
421     }
422
423     $function = !$update "new Ajax.Request(" : "new Ajax.Updater($update, ";
424
425     $function .= '\''.url_for($options['url']).'\'';
426     $function .= ', '.$javascript_options.')';
427
428     if (isset($options['before']))
429     {
430       $function = $options['before'].'; '.$function;
431     }
432     if (isset($options['after']))
433     {
434       $function = $function.'; '.$options['after'];
435     }
436     if (isset($options['condition']))
437     {
438       $function = 'if ('.$options['condition'].') { '.$function.'; }';
439     }
440     if (isset($options['confirm']))
441     {
442       $function = "if (window.confirm('".escape_javascript($options['confirm'])."')) { $function; }";
443       if (isset($options['cancel']))
444       {
445         $function = $function.' else { '.$options['cancel'].' }';
446       }
447     }
448
449     return $function.';';
450   }
451
452   /**
453    * Observes the field with the DOM ID specified by '$field_id' and makes
454    * an AJAX call when its contents have changed.
455    *
456    * Required '$options' are:
457    * 'url'                 'url_for()'-style options for the action to call
458    *                       when the field has changed.
459    *
460    * Additional options are:
461    * 'frequency'           The frequency (in seconds) at which changes to
462    *                       this field will be detected. Not setting this
463    *                       option at all or to a value equal to or less than
464    *                       zero will use event based observation instead of
465    *                       time based observation.
466    * 'update'              Specifies the DOM ID of the element whose
467    *                       innerHTML should be updated with the
468    *                       XMLHttpRequest response text.
469    * 'with'                A JavaScript expression specifying the
470    *                       parameters for the XMLHttpRequest. This defaults
471    *                       to 'value', which in the evaluated context
472    *                       refers to the new field value.
473    *
474    * Additionally, you may specify any of the options documented in
475    * link_to_remote().
476    */
477   function observe_field($field_id, $options = array())
478   {
479     sfContext::getInstance()->getResponse()->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
480
481     if (isset($options['frequency']) && $options['frequency'] > 0)
482     {
483       return _build_observer('Form.Element.Observer', $field_id, $options);
484     }
485     else
486     {
487       return _build_observer('Form.Element.EventObserver', $field_id, $options);
488     }
489   }
490
491   /**
492    * Like 'observe_field()', but operates on an entire form identified by the
493    * DOM ID '$form_id'. '$options' are the same as 'observe_field()', except
494    * the default value of the 'with' option evaluates to the
495    * serialized (request string) value of the form.
496    */
497   function observe_form($form_id, $options = array())
498   {
499     sfContext::getInstance()->getResponse()->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
500
501     if (isset($options['frequency']) && $options['frequency'] > 0)
502     {
503       return _build_observer('Form.Observer', $form_id, $options);
504     }
505     else
506     {
507       return _build_observer('Form.EventObserver', $form_id, $options);
508     }
509   }
510
511   /**
512    * Returns a JavaScript snippet to be used on the AJAX callbacks for starting
513    * visual effects.
514    *
515    * Example:
516    *  <?php echo link_to_remote('Reload', array(
517    *        'update'  => 'posts',
518    *        'url'     => '@reload',
519    *        'complete => visual_effect('highlight', 'posts', array('duration' => 0.5 )),
520    *  )) ?>
521    *
522    * If no '$element_id' is given, it assumes "element" which should be a local
523    * variable in the generated JavaScript execution context. This can be used
524    * for example with drop_receiving_element():
525    *
526    *  <?php echo drop_receving_element( ..., array(
527    *        ...
528    *        'loading' => visual_effect('fade'),
529    *  )) ?>
530    *
531    * This would fade the element that was dropped on the drop receiving element.
532    *
533    * You can change the behaviour with various options, see
534    * http://script.aculo.us for more documentation.
535    */
536   function visual_effect($name, $element_id = false, $js_options = array())
537   {
538     $response = sfContext::getInstance()->getResponse();
539     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
540     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/builder');
541     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
542
543     $element = $element_id ? "'$element_id'" : 'element';
544
545     if (in_array($name, array('toggle_appear', 'toggle_blind', 'toggle_slide')))
546     {
547       return "new Effect.toggle($element, '".substr($name, 7)."', "._options_for_javascript($js_options).");";
548     }
549     else
550     {
551       return "new Effect.".sfInflector::camelize($name)."($element, "._options_for_javascript($js_options).");";
552     }
553   }
554
555   /**
556    * Makes the elements with the DOM ID specified by '$element_id' sortable
557    * by drag-and-drop and make an AJAX call whenever the sort order has
558    * changed. By default, the action called gets the serialized sortable
559    * element as parameters.
560    *
561    * Example:
562    *   <php echo sortable_element($my_list, array(
563    *      'url' => '@order',
564    *   )) ?>
565    *
566    * In the example, the action gets a '$my_list' array parameter
567    * containing the values of the ids of elements the sortable consists
568    * of, in the current order.
569    *
570    * You can change the behaviour with various options, see
571    * http://script.aculo.us for more documentation.
572    */
573   function sortable_element($element_id, $options = array())
574   {
575     $response = sfContext::getInstance()->getResponse();
576     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
577     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/builder');
578     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
579     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/dragdrop');
580
581     if (!isset($options['with']))
582     {
583       $options['with'] = "Sortable.serialize('$element_id')";
584     }
585
586     if (!isset($options['onUpdate']))
587     {
588       $options['onUpdate'] = "function(){".remote_function($options)."}";
589     }
590
591     foreach (get_ajax_options() as $key)
592     {
593       unset($options[$key]);
594     }
595
596     foreach (array('tag', 'overlap', 'constraint', 'handle') as $option)
597     {
598       if (isset($options[$option]))
599       {
600         $options[$option] = "'{$options[$option]}'";
601       }
602     }
603
604     if (isset($options['containment']))
605     {
606       $options['containment'] = _array_or_string_for_javascript($options['containment']);
607     }
608
609     if (isset($options['hoverclass']))
610     {
611       $options['hoverclass'] = "'{$options['hoverclass']}'";
612     }
613
614     if (isset($options['only']))
615     {
616       $options['only'] = _array_or_string_for_javascript($options['only']);
617     }
618
619     return javascript_tag("Sortable.create('$element_id', "._options_for_javascript($options).")");
620   }
621
622   /**
623    * Makes the element with the DOM ID specified by '$element_id' draggable.
624    *
625    * Example:
626    *   <?php echo draggable_element('my_image', array(
627    *      'revert' => true,
628    *   )) ?>
629    *
630    * You can change the behaviour with various options, see
631    * http://script.aculo.us for more documentation.
632    */
633   function draggable_element($element_id, $options = array())
634   {
635     $response = sfContext::getInstance()->getResponse();
636     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
637     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/builder');
638     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
639     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/dragdrop');
640
641     return javascript_tag("new Draggable('$element_id', "._options_for_javascript($options).")");
642   }
643
644   /**
645    * Makes the element with the DOM ID specified by '$element_id' receive
646    * dropped draggable elements (created by 'draggable_element()') and make an AJAX call.
647    * By default, the action called gets the DOM ID of the element as parameter.
648    *
649    * Example:
650    *   <?php drop_receiving_element('my_cart', array(
651    *      'url' => 'cart/add',
652    *   )) ?>
653    *
654    * You can change the behaviour with various options, see
655    * http://script.aculo.us for more documentation.
656    */
657   function drop_receiving_element($element_id, $options = array())
658   {
659     $response = sfContext::getInstance()->getResponse();
660     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
661     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/builder');
662     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
663     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/dragdrop');
664
665     if (!isset($options['with']))
666     {
667       $options['with'] = "'id=' + encodeURIComponent(element.id)";
668     }
669     if (!isset($options['onDrop']))
670     {
671       $options['onDrop'] = "function(element){".remote_function($options)."}";
672     }
673
674     foreach (get_ajax_options() as $key)
675     {
676       unset($options[$key]);
677     }
678
679     if (isset($options['accept']))
680     {
681       $options['accept'] = _array_or_string_for_javascript($options['accept']);
682     }
683
684     if (isset($options['hoverclass']))
685     {
686       $options['hoverclass'] = "'{$options['hoverclass']}'";
687     }
688
689     return javascript_tag("Droppables.add('$element_id', "._options_for_javascript($options).")");
690   }
691
692   /**
693    * Returns a JavaScript tag with the '$content' inside.
694    * Example:
695    *   <?php echo javascript_tag("alert('All is good')") ?>
696    *   => <script type="text/javascript">alert('All is good')</script>
697    */
698   function javascript_tag($content)
699   {
700     return content_tag('script', javascript_cdata_section($content), array('type' => 'text/javascript'));
701   }
702
703   function javascript_cdata_section($content)
704   {
705     return "\n//".cdata_section("\n$content\n//")."\n";
706   }
707
708   /**
709    * wrapper for script.aculo.us/prototype Ajax.Autocompleter.
710    * @param string name value of input field
711    * @param string default value for input field
712    * @param array input tag options. (size, autocomplete, etc...)
713    * @param array completion options. (use_style, etc...)
714    *
715    * @return string input field tag, div for completion results, and
716    *                 auto complete javascript tags
717    */
718   function input_auto_complete_tag($name, $value, $url, $tag_options = array(), $completion_options = array())
719   {
720     $context = sfContext::getInstance();
721
722     $tag_options = _convert_options($tag_options);
723
724     $response = $context->getResponse();
725     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
726     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
727     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/controls');
728
729     $comp_options = _convert_options($completion_options);
730     if (isset($comp_options['use_style']) && $comp_options['use_style'] == true)
731     {
732       $response->addStylesheet(sfConfig::get('sf_prototype_web_dir').'/css/input_auto_complete_tag');
733     }
734
735     $tag_options['id'] = get_id_from_name(isset($tag_options['id']) ? $tag_options['id'] : $name);
736
737     $javascript  = input_tag($name, $value, $tag_options);
738     $javascript .= content_tag('div', '' , array('id' => $tag_options['id'].'_auto_complete', 'class' => 'auto_complete'));
739     $javascript .= _auto_complete_field($tag_options['id'], $url, $comp_options);
740
741     return $javascript;
742   }
743
744   /**
745    * wrapper for script.aculo.us/prototype Ajax.InPlaceEditor.
746    * @param string name id of field that can be edited
747    * @param string url of module/action to be called when ok is clicked
748    * @param array editor tag options. (rows, cols, highlightcolor, highlightendcolor, etc...)
749    *
750    * @return string javascript to manipulate the id field to allow click and edit functionality
751    */
752   function input_in_place_editor_tag($name, $url, $editor_options = array())
753   {
754     $response = sfContext::getInstance()->getResponse();
755     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
756     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
757     $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/controls');
758
759     $editor_options = _convert_options($editor_options);
760     $default_options = array('tag' => 'span', 'id' => '\''.$name.'_in_place_editor', 'class' => 'in_place_editor_field');
761
762     return _in_place_editor($name, $url, array_merge($default_options, $editor_options));
763   }
764
765   /**
766    * Mark the start of a block that should only be shown in the browser if JavaScript
767    * is switched on.
768    */
769   function if_javascript()
770   {
771     ob_start();
772   }
773
774   /**
775    * Mark the end of a block that should only be shown in the browser if JavaScript
776    * is switched on.
777    */
778   function end_if_javascript()
779   {
780     $content = ob_get_clean();
781
782     echo javascript_tag("document.write('" . esc_js_no_entities($content) . "');");
783   }
784
785   /*
786    * Makes an HTML element specified by the DOM ID '$field_id' become an in-place
787    * editor of a property.
788    *
789    * A form is automatically created and displayed when the user clicks the element,
790    * something like this:
791    * <form id="myElement-in-place-edit-form" target="specified url">
792    *   <input name="value" text="The content of myElement"/>
793    *   <input type="submit" value="ok"/>
794    *   <a onclick="javascript to cancel the editing">cancel</a>
795    * </form>
796    *
797    * The form is serialized and sent to the server using an AJAX call, the action on
798    * the server should process the value and return the updated value in the body of
799    * the reponse. The element will automatically be updated with the changed value
800    * (as returned from the server).
801    *
802    * Required '$options' are:
803    * 'url'                 Specifies the url where the updated value should
804    *                       be sent after the user presses "ok".
805    *
806    * Addtional '$options' are:
807    * 'rows'                Number of rows (more than 1 will use a TEXTAREA)
808    * 'cancel_text'         The text on the cancel link. (default: "cancel")
809    * 'save_text'           The text on the save link. (default: "ok")
810    * 'external_control'    The id of an external control used to enter edit mode.
811    * 'options'             Pass through options to the AJAX call (see prototype's Ajax.Updater)
812    * 'with'                JavaScript snippet that should return what is to be sent
813    *                       in the AJAX call, 'form' is an implicit parameter
814    */
815     function _in_place_editor($field_id, $url, $options = array())
816     {
817       $javascript = "new Ajax.InPlaceEditor(";
818
819       $javascript .= "'$field_id', ";
820       $javascript .= "'" . url_for($url) . "'";
821
822       $js_options = array();
823
824       if (isset($options['tokens'])) $js_options['tokens'] = _array_or_string_for_javascript($options['tokens']);
825
826       if (isset($options['cancel_text']))
827       {
828         $js_options['cancelText'] = "'".$options['cancel_text']."'";
829       }
830       if (isset($options['save_text']))
831       {
832         $js_options['okText'] = "'".$options['save_text']."'";
833       }
834       if (isset($options['cols']))
835       {
836         $js_options['cols'] = $options['cols'];
837       }
838       if (isset($options['rows']))
839       {
840         $js_options['rows'] = $options['rows'];
841       }
842       if (isset($options['external_control']))
843       {
844         $js_options['externalControl'] = "'".$options['external_control']."'";
845       }
846       if (isset($options['options']))
847       {
848         $js_options['ajaxOptions'] = $options['options'];
849       }
850       if (isset($options['with']))
851       {
852         $js_options['callback'] = "function(form, value) { return ".$options['with']." }";
853       }
854       if (isset($options['highlightcolor']))
855       {
856         $js_options['highlightcolor'] = "'".$options['highlightcolor']."'";
857       }
858       if (isset($options['highlightendcolor']))
859       {
860         $js_options['highlightendcolor'] = "'".$options['highlightendcolor']."'";
861       }
862       if (isset($options['loadTextURL']))
863       {
864         $js_options['loadTextURL'] =  "'".$options['loadTextURL']."'";
865       }
866
867       $javascript .= ', '._options_for_javascript($js_options);
868       $javascript .= ');';
869
870       return javascript_tag($javascript);
871     }
872
873   /**
874    * wrapper for script.aculo.us/prototype Ajax.Autocompleter.
875    * @param string id value of input field
876    * @param string url of module/action to execute for autocompletion
877    * @param array completion options
878    * @return string javascript tag for Ajax.Autocompleter
879    */
880   function _auto_complete_field($field_id, $url, $options = array())
881   {
882     $javascript = "new Ajax.Autocompleter(";
883
884     $javascript .= "'".get_id_from_name($field_id)."', ";
885     if (isset($options['update']))
886     {
887       $javascript .= "'".$options['update']."', ";
888     }
889     else
890     {
891       $javascript .= "'".get_id_from_name($field_id)."_auto_complete', ";
892     }
893
894     $javascript .= "'".url_for($url)."'";
895
896     $js_options = array();
897     if (isset($options['tokens']))
898     {
899       $js_options['tokens'] = _array_or_string_for_javascript($options['tokens']);
900     }
901     if (isset ($options['with']))
902     {
903       $js_options['callback'] = "function(element, value) { return ".$options['with']."}";
904     }
905     if (isset($options['indicator']))
906     {
907       $js_options['indicator']  = "'".$options['indicator']."'";
908     }
909     if (isset($options['on_show']))
910     {
911       $js_options['onShow'] = $options['on_show'];
912     }
913     if (isset($options['on_hide']))
914     {
915       $js_options['onHide'] = $options['on_hide'];
916     }
917     if (isset($options['min_chars']))
918     {
919       $js_options['minChars'] = $options['min_chars'];
920     }
921     if (isset($options['frequency']))
922     {
923       $js_options['frequency'] = $options['frequency'];
924     }
925     if (isset($options['update_element']))
926     {
927       $js_options['updateElement'] = $options['update_element'];
928     }
929     if (isset($options['after_update_element']))
930     {
931       $js_options['afterUpdateElement'] = $options['after_update_element'];
932     }
933     if (isset($options['param_name']))
934     {
935       $js_options['paramName'] = "'".$options['param_name']."'";
936     }
937
938     $javascript .= ', '._options_for_javascript($js_options).');';
939
940     return javascript_tag($javascript);
941   }
942
943   function _options_for_javascript($options)
944   {
945     $opts = array();
946     foreach ($options as $key => $value)
947     {
948       $opts[] = "$key:$value";
949     }
950     sort($opts);
951
952     return '{'.join(', ', $opts).'}';
953   }
954
955   function _array_or_string_for_javascript($option)
956   {
957     if (is_array($option))
958     {
959       return "['".join('\',\'', $option)."']";
960     }
961     else if ($option)
962     {
963       return "'$option'";
964     }
965   }
966
967   function _options_for_ajax($options)
968   {
969     $js_options = _build_callbacks($options);
970
971     $js_options['asynchronous'] = (isset($options['type']) && ($options['type'] == 'synchronous')) ? 'false' : 'true';
972     if (isset($options['method'])) $js_options['method'] = _method_option_to_s($options['method']);
973     if (isset($options['position'])) $js_options['insertion'] = "Insertion.".sfInflector::camelize($options['position']);
974     $js_options['evalScripts'] = (!isset($options['script']) || $options['script'] == '0' || $options['script'] == false) ? 'false' : 'true';
975
976     if (isset($options['form']))
977     {
978       $js_options['parameters'] = 'Form.serialize(this)';
979     }
980     else if (isset($options['submit']))
981     {
982       $js_options['parameters'] = "Form.serialize(document.getElementById('{$options['submit']}'))";
983     }
984     else if (isset($options['with']))
985     {
986       $js_options['parameters'] = $options['with'];
987     }
988
989     return _options_for_javascript($js_options);
990   }
991
992   function _method_option_to_s($method)
993   {
994     return (is_string($method) && $method[0] != "'") ? "'$method'" : $method;
995   }
996
997   function _build_observer($klass, $name, $options = array())
998   {
999     if (!isset($options['with']) && isset($options['update']))
1000     {
1001       $options['with'] = 'value';
1002     }
1003
1004     $callback = remote_function($options);
1005
1006     $javascript  = 'new '.$klass.'("'.$name.'", ';
1007     if (isset($options['frequency']) && $options['frequency'] > 0)
1008     {
1009       $javascript .= $options['frequency'].", ";
1010     }
1011     $javascript .= "function(element, value) {";
1012     $javascript .= $callback.'});';
1013
1014     return javascript_tag($javascript);
1015   }
1016
1017   function _build_callbacks($options)
1018   {
1019     $callbacks = array();
1020     foreach (get_callbacks() as $callback)
1021     {
1022       if (isset($options[$callback]))
1023       {
1024         $name = 'on'.ucfirst($callback);
1025         $code = $options[$callback];
1026         $callbacks[$name] = 'function(request, json){'.$code.'}';
1027       }
1028     }
1029
1030     return $callbacks;
1031   }
1032
Note: See TracBrowser for help on using the browser.