JavascriptHelper.php 35 KB

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