Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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