comparison cake/libs/view/helpers/ajax.php @ 0:261e66bd5a0c

hg init
author Shoshi TAMAKI <shoshi@cr.ie.u-ryukyu.ac.jp>
date Sun, 24 Jul 2011 21:08:31 +0900
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:261e66bd5a0c
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 /**
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 */
32 class 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 }