Development

/branches/1.1/lib/helper/JavascriptHelper.php

You must first sign up to be able to contribute.

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

Revision 17385, 38.8 kB (checked in by dwhittle, 5 years ago)

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