cakephp2-php8/libs/helpers/ajax.php

507 lines
19 KiB
PHP
Raw Normal View History

<?php
/* SVN FILE: $Id$ */
/**
* Helper for AJAX operations.
*
* Helps doing AJAX using the Prototype library.
*
* PHP versions 4 and 5
*
* CakePHP : Rapid Development Framework <http://www.cakephp.org/>
* Copyright (c) 2005, CakePHP Authors/Developers
*
* Author(s): Michal Tatarynowicz aka Pies <tatarynowicz@gmail.com>
* Larry E. Masters aka PhpNut <nut@phpnut.com>
* Kamil Dzielinski aka Brego <brego.dk@gmail.com>
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @filesource
* @author CakePHP Authors/Developers
* @copyright Copyright (c) 2005, CakePHP Authors/Developers
* @link https://trac.cakephp.org/wiki/Authors Authors/Developers
* @package cake
* @subpackage cake.libs.helpers
* @since CakePHP v 0.9.2
* @version $Revision$
* @modifiedby $LastChangedBy$
* @lastmodified $Date$
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
/**
* AjaxHelper helper library.
*
* Helps doing AJAX using the Prototype library.
*
* @package cake
* @subpackage cake.libs.helpers
* @since CakePHP v 0.9.2
*
*/
uses('helpers/html', 'helpers/javascript');
class AjaxHelper extends Helper
{
var $callbacks = array('uninitialized', 'loading', 'loaded', 'interactive', 'complete');
var $ajaxOptions = array('method','position','form','parameters','evalScripts', 'asynchronous', 'onComplete', 'onUninitialized', 'onLoading', 'onLoaded', 'onInteractive');
var $dragOptions = array('handle', 'revert', 'constraint', 'change');
var $dropOptions = array('accept', 'containment', 'overlap', 'greedy', 'hoverclass', 'onHover', 'onDrop');
var $sortOptions = array('tag', 'only', 'overlap', 'constraint', 'containment', 'handle', 'hoverClass', 'ghosting', 'dropOnEmpty', 'onUpdate', 'onChange');
/**
* Returns link to remote action
*
* Returns a link to a remote action defined by <i>options[url]</i>
* (using the urlFor format) that's called in the background using
* XMLHttpRequest. The result of that request can then be inserted into a
* DOM object whose id can be specified with <i>options[update]</i>.
* Usually, the result would be a partial prepared by the controller with
* either renderPartial or renderPartialCollection.
*
* Examples:
* <code>
* linkToRemote("Delete this post",
* array("update" => "posts", "url" => "delete/{$postid->id}"));
* linkToRemote(imageTag("refresh"),
* array("update" => "emails", "url" => "list_emails" ));
* </code>
*
* By default, these remote requests are processed asynchronous during
* which various callbacks can be triggered (for progress indicators and
* the likes).
*
* Example:
* <code>
* linkToRemote (word,
* array("url" => "undo", "n" => word_counter),
* array("complete" => "undoRequestCompleted(request)"));
* </code>
*
* The callbacks that may be specified are:
*
* - <i>loading</i>:: Called when the remote document is being
* loaded with data by the browser.
* - <i>loaded</i>:: Called when the browser has finished loading
* the remote document.
* - <i>interactive</i>:: Called when the user can interact with the
* remote document, even though it has not
* finished loading.
* - <i>complete</i>:: Called when the XMLHttpRequest is complete.
*
* If you for some reason or another need synchronous processing (that'll
* block the browser while the request is happening), you can specify
* <i>options[type] = synchronous</i>.
*
* You can customize further browser side call logic by passing
* in Javascript code snippets via some optional parameters. In
* their order of use these are:
*
* - <i>confirm</i>:: Adds confirmation dialog.
* -<i>condition</i>:: Perform remote request conditionally
* by this expression. Use this to
* describe browser-side conditions when
* request should not be initiated.
* - <i>before</i>:: Called before request is initiated.
* - <i>after</i>:: Called immediately after request was
* initiated and before <i>loading</i>.
*
* @param HtmlHelper $html The HtmlHelper object which is creating link.
* @param string $title Title of link
* @param array $options Options for JavaScript function
* @param array $html_options Options for link
* @return string HTML code for link to remote action
*/
function linkToRemote ($html, $title, $options = null, $html_options = null)
{
$html_options['onclick'] = $this->remoteFunction($html, $options);
$href = (!empty($options['fallback'])) ? $options['fallback'] : '#';
return $html->link($title, $href, $html_options);
}
/**
* Creates JavaScript function for remote AJAX call
*
* This function creates the javascript needed to make a remote call
* it is primarily used as a helper for linkToRemote.
*
* @see linkToRemote() for docs on options parameter.
*
* @param array $options options for javascript
* @return string html code for link to remote action
*/
function remoteFunction ($html, $options = null)
{
$javascript_options = $this->_optionsForAjax($options);
$func = isset($options['update']) ? "new Ajax.Updater('{$options['update']}'," : "new Ajax.Request(";
$func .= "'" . $html->url(isset($options['url']) ? $options['url'] : "") . "'";
$func .= "$javascript_options)";
if (isset($options['before']))
{
$func = "{$options['before']}; $function";
}
if (isset($options['after']))
{
Merging [920] [922] [929] [920] Small bugfix for after condition in AjaxHelper::remoteFunction [922] Fixed Ticket #224 Added patch from Ticket #221 Added patch from Ticket #222 Renamed renderMethod() to requestAction() the name fits better since we are really requesting another objects response. Added a default setting to turn of autoRender for the class you are requesting the action from, this will allow you to request an action and use the return how you like, instead of letting the object output the content directly to the browser if it would normally do so. [929] Adding fix for Itcket #225. Moved code for beforeFilters to first section of Controller::constructClasses(). Removed current implementaion of beforeFilters. This will be changed to pass a reference of the object to the filters through a core filters class. The core filter class will then load the filters and perform all request on the object in the order the filters are arranged in var $beforeFilters, each beforeFilter needs to be a class that the core filter class will create an instance of. If one of the filters fails, it will return the object, untouched and not try to process other filters, an failed var will be set on the controller. This will be done before data base is initialized for the current object that is being filtered. Modifed the requestAction() changes are noted below Using inside of a controller. Default $this->requestAction('/controller/action/argument/'); Request Object to render output directly $this->requestAction('/controller/action/argument/', a('render')); Using a helper object to make request inside of a view: Default $helpername->requestAction('/controller/action/argument/'); Request Object to render output directly $helpername->requestAction('/controller/action/argument/', a('render')); Using the View object "$this" in a view to make request: $this->requestAction('/controller/action/argument/'); Request Object to render output directly $this->requestAction('/controller/action/argument/', a('render')); git-svn-id: https://svn.cakephp.org/repo/trunk/cake@930 3807eeeb-6ff5-0310-8944-8be069107fe0
2005-09-21 05:48:09 +00:00
$func = "$func; {$options['after']};";
}
if (isset($options['condition']))
{
$func = "if ({$options['condition']}) { $func; }";
}
if (isset($options['confirm']))
{
$js = new JavascriptHelper;
$func = "if (confirm('" . $js->escapeScript($options['confirm']) . "')) { $func; } else { return false; }";
}
return $func;
}
/**
* Periodically call remote url via AJAX.
*
* Periodically calls the specified url (<i>options[url]</i>) every <i>options[frequency]</i> seconds (default is 10).
* Usually used to update a specified div (<i>options[update]</i>) with the results of the remote call.
* The options for specifying the target with url and defining callbacks is the same as linkToRemote.
*
* @param JavascriptHelper $script Script helper generating the block
* @param array $options Callback options
* @return string Javascript code
*/
function remoteTimer ($script, $options = null)
{
$frequency = (isset($options['frequency']))? $options['frequency'] : 10;
$code = "new PeriodicalExecuter(function() {" . $this->remote_function($options) . "}, $frequency)";
return $script->codeBlock($code);
}
/**
* Returns form tag that will submit using Ajax.
*
* Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular
* reloading POST arrangement. Even though it's using Javascript to serialize the form elements, the form submission
* will work just like a regular submission as viewed by the receiving side (all elements available in params).
* The options for specifying the target with :url and defining callbacks is the same as link_to_remote.
*
* @param HtmlHelper $html HtmlHelper creating this form
* @param JavascriptHelper $script JavascriptHelper creating this form
* @param string $id Form id
* @param array $options Callback options
* @return string JavaScript code
*/
function form($html, $script, $id, $options = null)
{
$options['id'] = $id;
//$options['html']['onsubmit'] = $this->remoteFunction($options) . "; return false;";
return $html->formTag(null, "post", $options) . $script->event("$('$id')", "submit", "function(){" . $this->remoteFunction($html, $options) . "; return false;}");
}
/**
* Returns a button input tag that will submit using Ajax
*
* Returns a button input tag that will submit form using XMLHttpRequest in the background instead of regular
* reloading POST arrangement. <i>options</i> argument is the same as in <i>form_remote_tag</i>
*
* @param HtmlHelper $html HtmlHelper creating this form
* @param string $name Input button name
* @param string $value Input button value
* @param array $options Callback options
* @return string Ajaxed input button
*/
function submit ($html, $name, $value, $options = null)
{
$options['with'] = 'Form.serialize(this.form)';
$options['html']['type'] = 'button';
$options['html']['onclick'] = $this->remoteFunction($html, $options) . "; return false;";
$options['html']['name'] = $name;
$options['html']['value'] = $value;
return $html->tag("input", $options['html'], false);
}
/**
* Observe field and call ajax on change.
*
* Observes the field with the DOM ID specified by <i>field_id</i> and makes
* an Ajax when its contents have changed.
*
* Required +options+ are:
* - <i>frequency</i>:: The frequency (in seconds) at which changes to
* this field will be detected.
* - <i>url</i>:: @see urlFor() -style options for the action to call
* when the field has changed.
*
* Additional options are:
* - <i>update</i>:: Specifies the DOM ID of the element whose
* innerHTML should be updated with the
* XMLHttpRequest response text.
* - <i>with</i>:: A Javascript expression specifying the
* parameters for the XMLHttpRequest. This defaults
* to Form.Element.serialize('$field_id'), which can be
* accessed from params['form']['field_id'].
*
* Additionally, you may specify any of the options documented in
* @see linkToRemote().
*
* @param JavaScriptHelper $script
* @param string $field_id DOM ID of field to observe
* @param array $options ajax options
* @return string ajax script
*/
function observeField ($html, $script, $field_id, $options = null)
{
if (!isset($options['with']))
{
$options['with'] = "Form.Element.serialize('$field_id')";
}
return $script->codeBlock($this->_buildObserver($html, 'Form.Element.Observer', $field_id, $options));
}
/**
* Observe entire form and call ajax on change.
*
* Like @see observeField(), but operates on an entire form identified by the
* DOM ID <b>form_id</b>. <b>options</b> are the same as <b>observe_field</b>, except
* the default value of the <i>with</i> option evaluates to the
* serialized (request string) value of the form.
*
* @param HtmlHelper $html
* @param JavaScriptHelper $script
* @param string $field_id DOM ID of field to observe
* @param array $options ajax options
* @return string ajax script
*/
function observeForm ($html, $script, $field_id, $options = array())
{
if (!isset($options['with']))
{
$options['with'] = 'Form.serialize(this.form)';
}
return $script->codeBlock($this->_buildObserver($html, 'Form.Observer', $field_id, $options));
}
/**
* Create a text field with Autocomplete.
*
* Creates an autocomplete field with the given ID and options.
*
* options['with'] defaults to "Form.Element.serialize('$field_id')",
* but can be any valid javascript expression defining the
*
* @param HtmlHelper $html
* @param JavascriptHelper $script
* @param string $field_id DOM ID of field to observe
* @param array $options ajax options
* @return string ajax script
*/
function autoComplete ($html, $script, $field, $url = "", $options = array())
{
if (!isset($options['id']))
{
$options['id'] = str_replace("/", "_", $field);
}
$htmlOptions = $options;
$ajaxOptions = array('with', 'asynchronous', 'synchronous', 'method', 'position', 'form');
$htmlOptions['autocomplete'] = "off";
foreach($ajaxOptions as $key)
{
if(isset($options[$key]))
{
$ajaxOptions[$key] = $options[$key];
}
else
{
unset($ajaxOptions[$key]);
}
}
$divOptions = array('id' => $options['id'] . "_autoComplete", 'class' => "auto_complete");
return $html->input($field, $htmlOptions) .
$html->tag("div", $divOptions, true) . "</div>" .
$script->codeBlock("new Ajax.Autocompleter('" . $options['id'] . "', '" . $divOptions['id'] . "', '" . $html->url($url) . "', " . $this->_optionsForAjax($ajaxOptions) . ");");
}
function drag($script, $id, $options = array())
{
$options = $this->_optionsForDraggable($options);
return $script->codeBlock("new Draggable('$id'$options);");
}
function _optionsForDraggable ($options)
{
$options = $this->_optionsToString($options, array('handle','constraint'));
return $this->_buildOptions($options, $this->dragOptions);
}
/**
* For a reference on the options for this function, check out
* http://wiki.script.aculo.us/scriptaculous/show/Droppables.add
*
*/
function drop($html, $script, $id, $options = array())
{
$options = $this->_optionsForDroppable($options);
return $script->codeBlock("Droppables.add('$id'$options);");
}
function _optionsForDroppable ($options)
{
$options = $this->_optionsToString($options, array('accept','overlap','hoverclass'));
return $this->_buildOptions($options, $this->dropOptions);
}
function dropRemote($html, $script, $id, $options = array(), $ajaxOptions = array())
{
$options['onDrop'] = "function(element){" . $this->remoteFunction($html, $ajaxOptions) . "}";
}
/**
* Makes a list or group of floated objects sortable.
*
*
* @param HtmlHelper $html
* @param JavaScriptHelper $script
* @param string $id DOM ID of parent
* @param array $options Array of options to control sort.http://wiki.script.aculo.us/scriptaculous/show/Sortable.create
* @link http://wiki.script.aculo.us/scriptaculous/show/Sortable.create
*/
function sortable($html, $script, $id, $options = array())
{
if (!empty($options['url']))
{
$options['with'] = "Sortable.serialize('$id')";
$options['onUpdate'] = 'function(sortable){'.$this->remoteFunction($html, $options).'}';
}
$options = $this->_optionsForSortable($options);
return $script->codeBlock("Sortable.create('$id'$options);");
}
function _optionsForSortable ($options)
{
$options = $this->_optionsToString($options, array('handle','tag','constraint','ghosting'));
return $this->_buildOptions($options, $this->sortOptions);
}
/**
* Javascript helper function (private).
*
*/
function _optionsForAjax ($options = array())
{
$js_options = $this->_buildCallbacks($options);
$js_options['asynchronous'] = 'true';
$options = $this->_optionsToString($options, array('method'));
foreach($options as $key => $value)
{
switch($key)
{
case 'type':
$js_options['asynchronous'] = ($value == 'synchronous') ? 'false' : 'true';
break;
case 'position':
$js_options['insertion'] = "Insertion." . Inflector::camelize($options['position']);
break;
case 'with':
$js_options['parameters'] = $options['with'];
break;
case 'form':
$js_options['parameters'] = 'Form.serialize(this)';
break;
}
}
return $this->_buildOptions($js_options, $this->ajaxOptions);
}
function _buildOptions ($options, $acceptable) {
if(is_array($options))
{
$out = array();
foreach ($options as $k => $v)
{
if(in_array($k, $acceptable))
{
$out[] = "$k:$v";
}
}
$out = join(', ', $out);
$out = ', {' . $out . '}';
return $out;
}
else
{
return false;
}
}
function _buildObserver ($html, $klass, $name, $options=null)
{
if(!isset($options['with']) && isset($options['update']))
{
$options['with'] = 'value';
}
$callback = $this->remoteFunction($html, $options);
$javascript = "new $klass('$name', ";
$javascript .= (isset($options['frequency']) ? $options['frequency'] : 2) . ", function(element, value) {";
$javascript .= "$callback})";
return $javascript;
}
function _buildCallbacks($options)
{
$callbacks = array();
foreach($this->callbacks as $callback)
{
if(isset($options[$callback]))
{
$name = 'on' . ucfirst($callback);
$code = $options[$callback];
$callbacks[$name] = "function(request){".$code."}";
}
}
return $callbacks;
}
function _optionsToString ($options, $stringOpts = array())
{
foreach ($stringOpts as $option)
{
if(isset($options[$option]) && !$options[$option][0] != "'")
{
$options[$option] = "'{$options[$option]}'";
}
}
return $options;
}
}
?>