PageRenderTime 50ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/libs/view/helpers/ajax.php

https://github.com/msadouni/cakephp2x
PHP | 1015 lines | 555 code | 87 blank | 373 comment | 102 complexity | 41c3ee36172589e54c292e85fef9d2b1 MD5 | raw file
  1. <?php
  2. /**
  3. * Helper for AJAX operations.
  4. *
  5. * Helps doing AJAX using the Prototype library.
  6. *
  7. * PHP Version 5.x
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. *
  12. * Licensed under The MIT License
  13. * Redistributions of files must retain the above copyright notice.
  14. *
  15. * @copyright Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package cake
  18. * @subpackage cake.cake.libs.view.helpers
  19. * @since CakePHP(tm) v 0.10.0.1076
  20. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  21. */
  22. /**
  23. * AjaxHelper helper library.
  24. *
  25. * Helps doing AJAX using the Prototype library.
  26. *
  27. * @package cake
  28. * @subpackage cake.cake.libs.view.helpers
  29. */
  30. class AjaxHelper extends AppHelper {
  31. /**
  32. * Included helpers.
  33. *
  34. * @var array
  35. */
  36. public $helpers = array('Html', 'Javascript', 'Form');
  37. /**
  38. * HtmlHelper instance
  39. *
  40. * @var HtmlHelper
  41. * @access public
  42. */
  43. public $Html = null;
  44. /**
  45. * JavaScriptHelper instance
  46. *
  47. * @var JavaScriptHelper
  48. * @access public
  49. */
  50. public $Javascript = null;
  51. /**
  52. * Names of Javascript callback functions.
  53. *
  54. * @var array
  55. */
  56. public $callbacks = array(
  57. 'complete', 'create', 'exception', 'failure', 'interactive', 'loading',
  58. 'loaded', 'success', 'uninitialized'
  59. );
  60. /**
  61. * Names of AJAX options.
  62. *
  63. * @var array
  64. */
  65. public $ajaxOptions = array(
  66. 'after', 'asynchronous', 'before', 'confirm', 'condition', 'contentType', 'encoding',
  67. 'evalScripts', 'failure', 'fallback', 'form', 'indicator', 'insertion', 'interactive',
  68. 'loaded', 'loading', 'method', 'onCreate', 'onComplete', 'onException', 'onFailure',
  69. 'onInteractive', 'onLoaded', 'onLoading', 'onSuccess', 'onUninitialized', 'parameters',
  70. 'position', 'postBody', 'requestHeaders', 'success', 'type', 'update', 'with'
  71. );
  72. /**
  73. * Options for draggable.
  74. *
  75. * @var array
  76. */
  77. public $dragOptions = array(
  78. 'handle', 'revert', 'snap', 'zindex', 'constraint', 'change', 'ghosting',
  79. 'starteffect', 'reverteffect', 'endeffect', 'scroll', 'scrollSensitivity',
  80. 'onStart', 'onDrag', 'onEnd'
  81. );
  82. /**
  83. * Options for droppable.
  84. *
  85. * @var array
  86. */
  87. public $dropOptions = array(
  88. 'accept', 'containment', 'greedy', 'hoverclass', 'onHover', 'onDrop', 'overlap'
  89. );
  90. /**
  91. * Options for sortable.
  92. *
  93. * @var array
  94. */
  95. public $sortOptions = array(
  96. 'constraint', 'containment', 'dropOnEmpty', 'ghosting', 'handle', 'hoverclass', 'onUpdate',
  97. 'onChange', 'only', 'overlap', 'scroll', 'scrollSensitivity', 'scrollSpeed', 'tag', 'tree',
  98. 'treeTag', 'update'
  99. );
  100. /**
  101. * Options for slider.
  102. *
  103. * @var array
  104. */
  105. public $sliderOptions = array(
  106. 'alignX', 'alignY', 'axis', 'disabled', 'handleDisabled', 'handleImage', 'increment',
  107. 'maximum', 'minimum', 'onChange', 'onSlide', 'range', 'sliderValue', 'values'
  108. );
  109. /**
  110. * Options for in-place editor.
  111. *
  112. * @var array
  113. */
  114. public $editorOptions = array(
  115. 'okText', 'cancelText', 'savingText', 'formId', 'externalControl', 'rows', 'cols', 'size',
  116. 'highlightcolor', 'highlightendcolor', 'savingClassName', 'formClassName', 'loadTextURL',
  117. 'loadingText', 'callback', 'ajaxOptions', 'clickToEditText', 'collection', 'okControl',
  118. 'cancelControl', 'submitOnBlur'
  119. );
  120. /**
  121. * Options for auto-complete editor.
  122. *
  123. * @var array
  124. */
  125. public $autoCompleteOptions = array(
  126. 'afterUpdateElement', 'callback', 'frequency', 'indicator', 'minChars', 'onShow', 'onHide',
  127. 'parameters', 'paramName', 'tokens', 'updateElement'
  128. );
  129. /**
  130. * Output buffer for Ajax update content
  131. *
  132. * @var array
  133. * @todo: Check visibility
  134. */
  135. private $__ajaxBuffer = array();
  136. /**
  137. * Returns link to remote action
  138. *
  139. * Returns a link to a remote action defined by <i>options[url]</i>
  140. * (using the url() format) that's called in the background using
  141. * XMLHttpRequest. The result of that request can then be inserted into a
  142. * DOM object whose id can be specified with <i>options[update]</i>.
  143. *
  144. * Examples:
  145. * <code>
  146. * link("Delete this post",
  147. * array("update" => "posts", "url" => "delete/{$postid->id}"));
  148. * link(imageTag("refresh"),
  149. * array("update" => "emails", "url" => "list_emails" ));
  150. * </code>
  151. *
  152. * By default, these remote requests are processed asynchronous during
  153. * which various callbacks can be triggered (for progress indicators and
  154. * the likes).
  155. *
  156. * Example:
  157. * <code>
  158. * link (word,
  159. * array("url" => "undo", "n" => word_counter),
  160. * array("complete" => "undoRequestCompleted(request)"));
  161. * </code>
  162. *
  163. * The callbacks that may be specified are:
  164. *
  165. * - <i>loading</i>:: Called when the remote document is being
  166. * loaded with data by the browser.
  167. * - <i>loaded</i>:: Called when the browser has finished loading
  168. * the remote document.
  169. * - <i>interactive</i>:: Called when the user can interact with the
  170. * remote document, even though it has not
  171. * finished loading.
  172. * - <i>complete</i>:: Called when the XMLHttpRequest is complete.
  173. *
  174. * If you for some reason or another need synchronous processing (that'll
  175. * block the browser while the request is happening), you can specify
  176. * <i>options[type] = synchronous</i>.
  177. *
  178. * You can customize further browser side call logic by passing
  179. * in Javascript code snippets via some optional parameters. In
  180. * their order of use these are:
  181. *
  182. * - <i>confirm</i>:: Adds confirmation dialog.
  183. * -<i>condition</i>:: Perform remote request conditionally
  184. * by this expression. Use this to
  185. * describe browser-side conditions when
  186. * request should not be initiated.
  187. * - <i>before</i>:: Called before request is initiated.
  188. * - <i>after</i>:: Called immediately after request was
  189. * initiated and before <i>loading</i>.
  190. *
  191. * @param string $title Title of link
  192. * @param string $href Href string "/products/view/12"
  193. * @param array $options Options for JavaScript function
  194. * @param string $confirm Confirmation message. Calls up a JavaScript confirm() message.
  195. * @param boolean $escapeTitle Escaping the title string to HTML entities
  196. *
  197. * @return string HTML code for link to remote action
  198. */
  199. public function link($title, $href = null, $options = array(), $confirm = null, $escapeTitle = true) {
  200. if (!isset($href)) {
  201. $href = $title;
  202. }
  203. if (!isset($options['url'])) {
  204. $options['url'] = $href;
  205. }
  206. if (isset($confirm)) {
  207. $options['confirm'] = $confirm;
  208. unset($confirm);
  209. }
  210. $htmlOptions = $this->__getHtmlOptions($options, array('url'));
  211. if (empty($options['fallback']) || !isset($options['fallback'])) {
  212. $options['fallback'] = $href;
  213. }
  214. $htmlDefaults = array('id' => 'link' . intval(mt_rand()), 'onclick' => '');
  215. $htmlOptions = array_merge($htmlDefaults, $htmlOptions);
  216. $htmlOptions['onclick'] .= ' event.returnValue = false; return false;';
  217. $return = $this->Html->link($title, $href, $htmlOptions, null, $escapeTitle);
  218. $callback = $this->remoteFunction($options);
  219. $script = $this->Javascript->event("'{$htmlOptions['id']}'", "click", $callback);
  220. if (is_string($script)) {
  221. $return .= $script;
  222. }
  223. return $return;
  224. }
  225. /**
  226. * Creates JavaScript function for remote AJAX call
  227. *
  228. * This function creates the javascript needed to make a remote call
  229. * it is primarily used as a helper for AjaxHelper::link.
  230. *
  231. * @param array $options options for javascript
  232. * @return string html code for link to remote action
  233. * @see AjaxHelper::link() for docs on options parameter.
  234. */
  235. public function remoteFunction($options) {
  236. if (isset($options['update'])) {
  237. if (!is_array($options['update'])) {
  238. $func = "new Ajax.Updater('{$options['update']}',";
  239. } else {
  240. $func = "new Ajax.Updater(document.createElement('div'),";
  241. }
  242. if (!isset($options['requestHeaders'])) {
  243. $options['requestHeaders'] = array();
  244. }
  245. if (is_array($options['update'])) {
  246. $options['update'] = implode(' ', $options['update']);
  247. }
  248. $options['requestHeaders']['X-Update'] = $options['update'];
  249. } else {
  250. $func = "new Ajax.Request(";
  251. }
  252. $func .= "'" . $this->url(isset($options['url']) ? $options['url'] : "") . "'";
  253. $func .= ", " . $this->__optionsForAjax($options) . ")";
  254. if (isset($options['before'])) {
  255. $func = "{$options['before']}; $func";
  256. }
  257. if (isset($options['after'])) {
  258. $func = "$func; {$options['after']};";
  259. }
  260. if (isset($options['condition'])) {
  261. $func = "if ({$options['condition']}) { $func; }";
  262. }
  263. if (isset($options['confirm'])) {
  264. $func = "if (confirm('" . $this->Javascript->escapeString($options['confirm'])
  265. . "')) { $func; } else { event.returnValue = false; return false; }";
  266. }
  267. return $func;
  268. }
  269. /**
  270. * Periodically call remote url via AJAX.
  271. *
  272. * Periodically calls the specified url (<i>options[url]</i>) every <i>options[frequency]</i>
  273. * seconds (default is 10). Usually used to update a specified div (<i>options[update]</i>) with
  274. * the results of the remote call. The options for specifying the target with url and defining
  275. * callbacks is the same as AjaxHelper::link().
  276. *
  277. * @param array $options Callback options
  278. * @return string Javascript code
  279. * @see AjaxHelper::link()
  280. */
  281. public function remoteTimer($options = null) {
  282. $frequency = (isset($options['frequency'])) ? $options['frequency'] : 10;
  283. $callback = $this->remoteFunction($options);
  284. $code = "new PeriodicalExecuter(function() {{$callback}}, $frequency)";
  285. return $this->Javascript->codeBlock($code);
  286. }
  287. /**
  288. * Returns form tag that will submit using Ajax.
  289. *
  290. * Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular
  291. * reloading POST arrangement. Even though it's using Javascript to serialize the form elements,
  292. * the form submission will work just like a regular submission as viewed by the receiving side
  293. * (all elements available in params). The options for defining callbacks is the same
  294. * as AjaxHelper::link().
  295. *
  296. * @param mixed $params Either a string identifying the form target, or an array of method parameters, including:
  297. * - 'params' => Acts as the form target
  298. * - 'type' => 'post' or 'get'
  299. * - 'options' => An array containing all HTML and script options used to
  300. * generate the form tag and Ajax request.
  301. * @param array $type How form data is posted: 'get' or 'post'
  302. * @param array $options Callback/HTML options
  303. * @return string JavaScript/HTML code
  304. * @see AjaxHelper::link()
  305. */
  306. public function form($params = null, $type = 'post', $options = array()) {
  307. $model = false;
  308. if (is_array($params)) {
  309. extract($params, EXTR_OVERWRITE);
  310. }
  311. if (empty($options['url'])) {
  312. $options['url'] = array('action' => $params);
  313. }
  314. $htmlDefaults = array(
  315. 'id' => 'form' . intval(mt_rand()),
  316. 'onsubmit' => "event.returnValue = false; return false;",
  317. 'type' => $type
  318. );
  319. $htmlOptions = $this->__getHtmlOptions($options, array('model', 'with'));
  320. $htmlOptions = array_merge($htmlDefaults, $htmlOptions);
  321. $defaults = array('model' => $model, 'with' => "Form.serialize('{$htmlOptions['id']}')");
  322. $options = array_merge($defaults, $options);
  323. $callback = $this->remoteFunction($options);
  324. $form = $this->Form->create($options['model'], $htmlOptions);
  325. $script = $this->Javascript->event("'" . $htmlOptions['id']. "'", 'submit', $callback);
  326. return $form . $script;
  327. }
  328. /**
  329. * Returns a button input tag that will submit using Ajax
  330. *
  331. * Returns a button input tag that will submit form using XMLHttpRequest in the background instead
  332. * of regular reloading POST arrangement. <i>options</i> argument is the same as
  333. * in AjaxHelper::form().
  334. *
  335. * @param string $title Input button title
  336. * @param array $options Callback options
  337. * @return string Ajaxed input button
  338. * @see AjaxHelper::form()
  339. */
  340. public function submit($title = 'Submit', $options = array()) {
  341. $htmlOptions = $this->__getHtmlOptions($options);
  342. $htmlOptions['value'] = $title;
  343. if (!isset($options['with'])) {
  344. $options['with'] = 'Form.serialize(Event.element(event).form)';
  345. }
  346. if (!isset($htmlOptions['id'])) {
  347. $htmlOptions['id'] = 'submit' . intval(mt_rand());
  348. }
  349. $htmlOptions['onclick'] = "event.returnValue = false; return false;";
  350. $callback = $this->remoteFunction($options);
  351. $form = $this->Form->submit($title, $htmlOptions);
  352. $script = $this->Javascript->event('"' . $htmlOptions['id'] . '"', 'click', $callback);
  353. return $form . $script;
  354. }
  355. /**
  356. * Observe field and call ajax on change.
  357. *
  358. * Observes the field with the DOM ID specified by <i>field</i> and makes
  359. * an Ajax when its contents have changed.
  360. *
  361. * Required +options+ are:
  362. * - <i>frequency</i>:: The frequency (in seconds) at which changes to
  363. * this field will be detected.
  364. * - <i>url</i>:: @see url() -style options for the action to call
  365. * when the field has changed.
  366. *
  367. * Additional options are:
  368. * - <i>update</i>:: Specifies the DOM ID of the element whose
  369. * innerHTML should be updated with the
  370. * XMLHttpRequest response text.
  371. * - <i>with</i>:: A Javascript expression specifying the
  372. * parameters for the XMLHttpRequest. This defaults
  373. * to Form.Element.serialize('$field'), which can be
  374. * accessed from params['form']['field_id'].
  375. *
  376. * Additionally, you may specify any of the options documented in
  377. * @see linkToRemote().
  378. *
  379. * @param string $field DOM ID of field to observe
  380. * @param array $options ajax options
  381. * @return string ajax script
  382. */
  383. public function observeField($field, $options = array()) {
  384. if (!isset($options['with'])) {
  385. $options['with'] = 'Form.Element.serialize(\'' . $field . '\')';
  386. }
  387. $observer = 'Observer';
  388. if (!isset($options['frequency']) || intval($options['frequency']) == 0) {
  389. $observer = 'EventObserver';
  390. }
  391. return $this->Javascript->codeBlock(
  392. $this->_buildObserver('Form.Element.' . $observer, $field, $options)
  393. );
  394. }
  395. /**
  396. * Observe entire form and call ajax on change.
  397. *
  398. * Like @see observeField(), but operates on an entire form identified by the
  399. * DOM ID <b>form</b>. <b>options</b> are the same as <b>observeField</b>, except
  400. * the default value of the <i>with</i> option evaluates to the
  401. * serialized (request string) value of the form.
  402. *
  403. * @param string $form DOM ID of form to observe
  404. * @param array $options ajax options
  405. * @return string ajax script
  406. */
  407. public function observeForm($form, $options = array()) {
  408. if (!isset($options['with'])) {
  409. $options['with'] = 'Form.serialize(\'' . $form . '\')';
  410. }
  411. $observer = 'Observer';
  412. if (!isset($options['frequency']) || intval($options['frequency']) == 0) {
  413. $observer = 'EventObserver';
  414. }
  415. return $this->Javascript->codeBlock(
  416. $this->_buildObserver('Form.' . $observer, $form, $options)
  417. );
  418. }
  419. /**
  420. * Create a text field with Autocomplete.
  421. *
  422. * Creates an autocomplete field with the given ID and options.
  423. *
  424. * options['with'] defaults to "Form.Element.serialize('$field')",
  425. * but can be any valid javascript expression defining the additional fields.
  426. *
  427. * @param string $field DOM ID of field to observe
  428. * @param string $url URL for the autocomplete action
  429. * @param array $options Ajax options
  430. * @return string Ajax script
  431. */
  432. public function autoComplete($field, $url = "", $options = array()) {
  433. $var = '';
  434. if (isset($options['var'])) {
  435. $var = 'var ' . $options['var'] . ' = ';
  436. unset($options['var']);
  437. }
  438. if (!isset($options['id'])) {
  439. $options['id'] = Inflector::camelize(str_replace(".", "_", $field));
  440. }
  441. $divOptions = array(
  442. 'id' => $options['id'] . "_autoComplete",
  443. 'class' => isset($options['class']) ? $options['class'] : 'auto_complete'
  444. );
  445. if (isset($options['div_id'])) {
  446. $divOptions['id'] = $options['div_id'];
  447. unset($options['div_id']);
  448. }
  449. $htmlOptions = $this->__getHtmlOptions($options);
  450. $htmlOptions['autocomplete'] = "off";
  451. foreach ($this->autoCompleteOptions as $opt) {
  452. unset($htmlOptions[$opt]);
  453. }
  454. if (isset($options['tokens'])) {
  455. if (is_array($options['tokens'])) {
  456. $options['tokens'] = $this->Javascript->object($options['tokens']);
  457. } else {
  458. $options['tokens'] = '"' . $options['tokens'] . '"';
  459. }
  460. }
  461. $options = $this->_optionsToString($options, array('paramName', 'indicator'));
  462. $options = $this->_buildOptions($options, $this->autoCompleteOptions);
  463. $text = $this->Form->text($field, $htmlOptions);
  464. $div = $this->Html->div(null, '', $divOptions);
  465. $script = "{$var}new Ajax.Autocompleter('{$htmlOptions['id']}', '{$divOptions['id']}', '";
  466. $script .= $this->Html->url($url) . "', {$options});";
  467. return "{$text}\n{$div}\n" . $this->Javascript->codeBlock($script);
  468. }
  469. /**
  470. * Creates an Ajax-updateable DIV element
  471. *
  472. * @param string $id options for javascript
  473. * @return string HTML code
  474. */
  475. public function div($id, $options = array()) {
  476. if (env('HTTP_X_UPDATE') != null) {
  477. $this->Javascript->enabled = false;
  478. $divs = explode(' ', env('HTTP_X_UPDATE'));
  479. if (in_array($id, $divs)) {
  480. @ob_end_clean();
  481. ob_start();
  482. return '';
  483. }
  484. }
  485. $attr = $this->_parseAttributes(array_merge($options, array('id' => $id)));
  486. return sprintf($this->Html->tags['blockstart'], $attr);
  487. }
  488. /**
  489. * Closes an Ajax-updateable DIV element
  490. *
  491. * @param string $id The DOM ID of the element
  492. * @return string HTML code
  493. */
  494. public function divEnd($id) {
  495. if (env('HTTP_X_UPDATE') != null) {
  496. $divs = explode(' ', env('HTTP_X_UPDATE'));
  497. if (in_array($id, $divs)) {
  498. $this->__ajaxBuffer[$id] = ob_get_contents();
  499. ob_end_clean();
  500. ob_start();
  501. return '';
  502. }
  503. }
  504. return $this->Html->tags['blockend'];
  505. }
  506. /**
  507. * Detects Ajax requests
  508. *
  509. * @return boolean True if the current request is a Prototype Ajax update call
  510. */
  511. public function isAjax() {
  512. return (isset($this->params['isAjax']) && $this->params['isAjax'] === true);
  513. }
  514. /**
  515. * Creates a draggable element. For a reference on the options for this function,
  516. * check out http://github.com/madrobby/scriptaculous/wikis/draggable
  517. *
  518. * @param unknown_type $id
  519. * @param array $options
  520. * @return unknown
  521. */
  522. public function drag($id, $options = array()) {
  523. $var = '';
  524. if (isset($options['var'])) {
  525. $var = 'var ' . $options['var'] . ' = ';
  526. unset($options['var']);
  527. }
  528. $options = $this->_buildOptions(
  529. $this->_optionsToString($options, array('handle', 'constraint')), $this->dragOptions
  530. );
  531. return $this->Javascript->codeBlock("{$var}new Draggable('$id', " .$options . ");");
  532. }
  533. /**
  534. * For a reference on the options for this function, check out
  535. * http://github.com/madrobby/scriptaculous/wikis/droppables
  536. *
  537. * @param unknown_type $id
  538. * @param array $options
  539. * @return string
  540. */
  541. public function drop($id, $options = array()) {
  542. $optionsString = array('overlap', 'hoverclass');
  543. if (!isset($options['accept']) || !is_array($options['accept'])) {
  544. $optionsString[] = 'accept';
  545. } else if (isset($options['accept'])) {
  546. $options['accept'] = $this->Javascript->object($options['accept']);
  547. }
  548. $options = $this->_buildOptions(
  549. $this->_optionsToString($options, $optionsString), $this->dropOptions
  550. );
  551. return $this->Javascript->codeBlock("Droppables.add('{$id}', {$options});");
  552. }
  553. /**
  554. * Make an element with the given $id droppable, and trigger an Ajax call when a draggable is
  555. * dropped on it.
  556. *
  557. * For a reference on the options for this function, check out
  558. * http://wiki.script.aculo.us/scriptaculous/show/Droppables.add
  559. *
  560. * @param string $id
  561. * @param array $options
  562. * @param array $ajaxOptions
  563. * @return string JavaScript block to create a droppable element
  564. */
  565. public function dropRemote($id, $options = array(), $ajaxOptions = array()) {
  566. $callback = $this->remoteFunction($ajaxOptions);
  567. $options['onDrop'] = "function(element, droppable, event) {{$callback}}";
  568. $optionsString = array('overlap', 'hoverclass');
  569. if (!isset($options['accept']) || !is_array($options['accept'])) {
  570. $optionsString[] = 'accept';
  571. } else if (isset($options['accept'])) {
  572. $options['accept'] = $this->Javascript->object($options['accept']);
  573. }
  574. $options = $this->_buildOptions(
  575. $this->_optionsToString($options, $optionsString),
  576. $this->dropOptions
  577. );
  578. return $this->Javascript->codeBlock("Droppables.add('{$id}', {$options});");
  579. }
  580. /**
  581. * Makes a slider control.
  582. *
  583. * @param string $id DOM ID of slider handle
  584. * @param string $trackId DOM ID of slider track
  585. * @param array $options Array of options to control the slider
  586. * @link http://github.com/madrobby/scriptaculous/wikis/slider
  587. */
  588. public function slider($id, $trackId, $options = array()) {
  589. if (isset($options['var'])) {
  590. $var = 'var ' . $options['var'] . ' = ';
  591. unset($options['var']);
  592. } else {
  593. $var = 'var ' . $id . ' = ';
  594. }
  595. $options = $this->_optionsToString($options, array(
  596. 'axis', 'handleImage', 'handleDisabled'
  597. ));
  598. $callbacks = array('change', 'slide');
  599. foreach ($callbacks as $callback) {
  600. if (isset($options[$callback])) {
  601. $call = $options[$callback];
  602. $options['on' . ucfirst($callback)] = "function(value) {{$call}}";
  603. unset($options[$callback]);
  604. }
  605. }
  606. if (isset($options['values']) && is_array($options['values'])) {
  607. $options['values'] = $this->Javascript->object($options['values']);
  608. }
  609. $options = $this->_buildOptions($options, $this->sliderOptions);
  610. $script = "{$var}new Control.Slider('$id', '$trackId', $options);";
  611. return $this->Javascript->codeBlock($script);
  612. }
  613. /**
  614. * Makes an Ajax In Place editor control.
  615. *
  616. * @param string $id DOM ID of input element
  617. * @param string $url Postback URL of saved data
  618. * @param array $options Array of options to control the editor, including ajaxOptions (see link).
  619. * @link http://github.com/madrobby/scriptaculous/wikis/ajax-inplaceeditor
  620. */
  621. public function editor($id, $url, $options = array()) {
  622. $url = $this->url($url);
  623. $options['ajaxOptions'] = $this->__optionsForAjax($options);
  624. foreach ($this->ajaxOptions as $opt) {
  625. if (isset($options[$opt])) {
  626. unset($options[$opt]);
  627. }
  628. }
  629. if (isset($options['callback'])) {
  630. $options['callback'] = 'function(form, value) {' . $options['callback'] . '}';
  631. }
  632. $type = 'InPlaceEditor';
  633. if (isset($options['collection']) && is_array($options['collection'])) {
  634. $options['collection'] = $this->Javascript->object($options['collection']);
  635. $type = 'InPlaceCollectionEditor';
  636. }
  637. $var = '';
  638. if (isset($options['var'])) {
  639. $var = 'var ' . $options['var'] . ' = ';
  640. unset($options['var']);
  641. }
  642. $options = $this->_optionsToString($options, array(
  643. 'okText', 'cancelText', 'savingText', 'formId', 'externalControl', 'highlightcolor',
  644. 'highlightendcolor', 'savingClassName', 'formClassName', 'loadTextURL', 'loadingText',
  645. 'clickToEditText', 'okControl', 'cancelControl'
  646. ));
  647. $options = $this->_buildOptions($options, $this->editorOptions);
  648. $script = "{$var}new Ajax.{$type}('{$id}', '{$url}', {$options});";
  649. return $this->Javascript->codeBlock($script);
  650. }
  651. /**
  652. * Makes a list or group of floated objects sortable.
  653. *
  654. * @param string $id DOM ID of parent
  655. * @param array $options Array of options to control sort.
  656. * @link http://github.com/madrobby/scriptaculous/wikis/sortable
  657. */
  658. public function sortable($id, $options = array()) {
  659. if (!empty($options['url'])) {
  660. if (empty($options['with'])) {
  661. $options['with'] = "Sortable.serialize('$id')";
  662. }
  663. $options['onUpdate'] = 'function(sortable) {' . $this->remoteFunction($options) . '}';
  664. }
  665. $block = true;
  666. if (isset($options['block'])) {
  667. $block = $options['block'];
  668. unset($options['block']);
  669. }
  670. $strings = array(
  671. 'tag', 'constraint', 'only', 'handle', 'hoverclass', 'tree',
  672. 'treeTag', 'update', 'overlap'
  673. );
  674. $scrollIsObject = (
  675. isset($options['scroll']) &&
  676. $options['scroll'] != 'window' &&
  677. strpos($options['scroll'], '$(') !== 0
  678. );
  679. if ($scrollIsObject) {
  680. $strings[] = 'scroll';
  681. }
  682. $options = $this->_optionsToString($options, $strings);
  683. $options = array_merge($options, $this->_buildCallbacks($options));
  684. $options = $this->_buildOptions($options, $this->sortOptions);
  685. $result = "Sortable.create('$id', $options);";
  686. if (!$block) {
  687. return $result;
  688. }
  689. return $this->Javascript->codeBlock($result);
  690. }
  691. /**
  692. * Private helper function for Javascript.
  693. *
  694. * @param array $options Set of options
  695. * @access private
  696. */
  697. private function __optionsForAjax($options) {
  698. if (isset($options['indicator'])) {
  699. if (isset($options['loading'])) {
  700. $loading = $options['loading'];
  701. if (!empty($loading) && substr(trim($loading), -1, 1) != ';') {
  702. $options['loading'] .= '; ';
  703. }
  704. $options['loading'] .= "Element.show('{$options['indicator']}');";
  705. } else {
  706. $options['loading'] = "Element.show('{$options['indicator']}');";
  707. }
  708. if (isset($options['complete'])) {
  709. $complete = $options['complete'];
  710. if (!empty($complete) && substr(trim($complete), -1, 1) != ';') {
  711. $options['complete'] .= '; ';
  712. }
  713. $options['complete'] .= "Element.hide('{$options['indicator']}');";
  714. } else {
  715. $options['complete'] = "Element.hide('{$options['indicator']}');";
  716. }
  717. unset($options['indicator']);
  718. }
  719. $jsOptions = array_merge(
  720. array('asynchronous' => 'true', 'evalScripts' => 'true'),
  721. $this->_buildCallbacks($options)
  722. );
  723. $options = $this->_optionsToString($options, array(
  724. 'contentType', 'encoding', 'fallback', 'method', 'postBody', 'update', 'url'
  725. ));
  726. $jsOptions = array_merge($jsOptions, array_intersect_key($options, array_flip(array(
  727. 'contentType', 'encoding', 'method', 'postBody'
  728. ))));
  729. foreach ($options as $key => $value) {
  730. switch ($key) {
  731. case 'type':
  732. $jsOptions['asynchronous'] = ($value == 'synchronous') ? 'false' : 'true';
  733. break;
  734. case 'evalScripts':
  735. $jsOptions['evalScripts'] = ($value) ? 'true' : 'false';
  736. break;
  737. case 'position':
  738. $pos = Inflector::camelize($options['position']);
  739. $jsOptions['insertion'] = "Insertion.{$pos}";
  740. break;
  741. case 'with':
  742. $jsOptions['parameters'] = $options['with'];
  743. break;
  744. case 'form':
  745. $jsOptions['parameters'] = 'Form.serialize(this)';
  746. break;
  747. case 'requestHeaders':
  748. $keys = array();
  749. foreach ($value as $key => $val) {
  750. $keys[] = "'" . $key . "'";
  751. $keys[] = "'" . $val . "'";
  752. }
  753. $jsOptions['requestHeaders'] = '[' . implode(', ', $keys) . ']';
  754. break;
  755. }
  756. }
  757. return $this->_buildOptions($jsOptions, $this->ajaxOptions);
  758. }
  759. /**
  760. * Private Method to return a string of html options
  761. * option data as a JavaScript options hash.
  762. *
  763. * @param array $options Options in the shape of keys and values
  764. * @param array $extra Array of legal keys in this options context
  765. * @return array Array of html options
  766. * @access private
  767. */
  768. private function __getHtmlOptions($options, $extra = array()) {
  769. foreach (array_merge($this->ajaxOptions, $this->callbacks, $extra) as $key) {
  770. if (isset($options[$key])) {
  771. unset($options[$key]);
  772. }
  773. }
  774. return $options;
  775. }
  776. /**
  777. * Returns a string of JavaScript with the given option data as a JavaScript options hash.
  778. *
  779. * @param array $options Options in the shape of keys and values
  780. * @param array $acceptable Array of legal keys in this options context
  781. * @return string String of Javascript array definition
  782. */
  783. protected function _buildOptions($options, $acceptable) {
  784. if (is_array($options)) {
  785. $out = array();
  786. foreach ($options as $k => $v) {
  787. if (in_array($k, $acceptable)) {
  788. if ($v === true) {
  789. $v = 'true';
  790. } elseif ($v === false) {
  791. $v = 'false';
  792. }
  793. $out[] = "$k:$v";
  794. } elseif ($k === 'with' && in_array('parameters', $acceptable)) {
  795. $out[] = "parameters:${v}";
  796. }
  797. }
  798. $out = implode(', ', $out);
  799. $out = '{' . $out . '}';
  800. return $out;
  801. } else {
  802. return false;
  803. }
  804. }
  805. /**
  806. * Return JavaScript text for an observer...
  807. *
  808. * @param string $klass Name of JavaScript class
  809. * @param string $name
  810. * @param array $options Ajax options
  811. * @return string Formatted JavaScript
  812. */
  813. protected function _buildObserver($klass, $name, $options = null) {
  814. if (!isset($options['with']) && isset($options['update'])) {
  815. $options['with'] = 'value';
  816. }
  817. $callback = $this->remoteFunction($options);
  818. $hasFrequency = !(!isset($options['frequency']) || intval($options['frequency']) == 0);
  819. $frequency = $hasFrequency ? $options['frequency'] . ', ' : '';
  820. return "new $klass('$name', {$frequency}function(element, value) {{$callback}})";
  821. }
  822. /**
  823. * Return Javascript text for callbacks.
  824. *
  825. * @param array $options Option array where a callback is specified
  826. * @return array Options with their callbacks properly set
  827. * @access protected
  828. */
  829. protected function _buildCallbacks($options) {
  830. $callbacks = array();
  831. foreach ($this->callbacks as $callback) {
  832. if (isset($options[$callback])) {
  833. $name = 'on' . ucfirst($callback);
  834. $code = $options[$callback];
  835. switch ($name) {
  836. case 'onComplete':
  837. $callbacks[$name] = "function(request, json) {" . $code . "}";
  838. break;
  839. case 'onCreate':
  840. $callbacks[$name] = "function(request, xhr) {" . $code . "}";
  841. break;
  842. case 'onException':
  843. $callbacks[$name] = "function(request, exception) {" . $code . "}";
  844. break;
  845. default:
  846. $callbacks[$name] = "function(request) {" . $code . "}";
  847. break;
  848. }
  849. if (isset($options['bind'])) {
  850. $bind = $options['bind'];
  851. $hasBinding = (
  852. (is_array($bind) && in_array($callback, $bind)) ||
  853. (is_string($bind) && strpos($bind, $callback) !== false)
  854. );
  855. if ($hasBinding) {
  856. $callbacks[$name] .= ".bind(this)";
  857. }
  858. }
  859. }
  860. }
  861. return $callbacks;
  862. }
  863. /**
  864. * Returns a string of JavaScript with a string representation of given options array.
  865. *
  866. * @param array $options Ajax options array
  867. * @param array $stringOpts Options as strings in an array
  868. * @access private
  869. * @return array
  870. */
  871. protected function _optionsToString($options, $stringOpts = array()) {
  872. foreach ($stringOpts as $option) {
  873. $hasOption = (
  874. isset($options[$option]) && !empty($options[$option]) &&
  875. is_string($options[$option]) && $options[$option][0] != "'"
  876. );
  877. if ($hasOption) {
  878. if ($options[$option] === true || $options[$option] === 'true') {
  879. $options[$option] = 'true';
  880. } elseif ($options[$option] === false || $options[$option] === 'false') {
  881. $options[$option] = 'false';
  882. } else {
  883. $options[$option] = "'{$options[$option]}'";
  884. }
  885. }
  886. }
  887. return $options;
  888. }
  889. /**
  890. * Executed after a view has rendered, used to include bufferred code
  891. * blocks.
  892. *
  893. * @access public
  894. */
  895. public function afterRender() {
  896. if (env('HTTP_X_UPDATE') != null && !empty($this->__ajaxBuffer)) {
  897. @ob_end_clean();
  898. $data = array();
  899. $divs = explode(' ', env('HTTP_X_UPDATE'));
  900. $keys = array_keys($this->__ajaxBuffer);
  901. if (count($divs) == 1 && in_array($divs[0], $keys)) {
  902. echo $this->__ajaxBuffer[$divs[0]];
  903. } else {
  904. foreach ($this->__ajaxBuffer as $key => $val) {
  905. if (in_array($key, $divs)) {
  906. $data[] = $key . ':"' . rawurlencode($val) . '"';
  907. }
  908. }
  909. $out = 'var __ajaxUpdater__ = {' . implode(", \n", $data) . '};' . "\n";
  910. $out .= 'for (n in __ajaxUpdater__) { if (typeof __ajaxUpdater__[n] == "string"';
  911. $out .= ' && $(n)) Element.update($(n), unescape(decodeURIComponent(';
  912. $out .= '__ajaxUpdater__[n]))); }';
  913. echo $this->Javascript->codeBlock($out, false);
  914. }
  915. $scripts = $this->Javascript->getCache();
  916. if (!empty($scripts)) {
  917. echo $this->Javascript->codeBlock($scripts, false);
  918. }
  919. $this->_stop();
  920. }
  921. }
  922. }
  923. ?>