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 | */ |
---|
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 | } |
---|