PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

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

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