source: Dev/branches/cakephp/cake/libs/view/helpers/ajax.php @ 126

Last change on this file since 126 was 126, checked in by fpvanagthoven, 14 years ago

Cakephp branch.

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