Mercurial > hg > Members > shoshi > webvirt
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 } |