From 12075ec7c1dd69c901bee0f3f72c19464de625ae Mon Sep 17 00:00:00 2001 From: "mariano.iglesias" Date: Sun, 23 Dec 2007 21:08:54 +0000 Subject: [PATCH] Adding contentType, encoding and postBody AJAX options. Adding onCreate (specify 'create' in $options) and onException (specify 'exception' in $options) callbacks. All callbacks should be specified using their aliases (so use 'complete' => 'completeFunc();' for onComplete callback, etc.) Adding tests for AJAX helper. Closes #1244, closes #2644, closes #2856. git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6234 3807eeeb-6ff5-0310-8944-8be069107fe0 --- cake/libs/view/helpers/ajax.php | 71 ++++++----- .../cases/libs/view/helpers/ajax.test.php | 111 +++++++++++++++--- 2 files changed, 134 insertions(+), 48 deletions(-) diff --git a/cake/libs/view/helpers/ajax.php b/cake/libs/view/helpers/ajax.php index f11aaca41..3dd290f84 100644 --- a/cake/libs/view/helpers/ajax.php +++ b/cake/libs/view/helpers/ajax.php @@ -37,29 +37,38 @@ * @subpackage cake.cake.libs.view.helpers */ class AjaxHelper extends AppHelper { - /** * Included helpers. * * @var array */ var $helpers = array('Html', 'Javascript', 'Form'); - +/** + * HtmlHelper instance + * + * @var object + * @access public + */ var $Html = null; - +/** + * JavaScriptHelper instance + * + * @var object + * @access public + */ var $Javascript = null; /** * Names of Javascript callback functions. * * @var array */ - var $callbacks = array('uninitialized', 'loading', 'loaded', 'interactive', 'complete', 'success', 'failure'); + var $callbacks = array('complete', 'create', 'exception', 'failure', 'interactive', 'loading', 'loaded', 'success', 'uninitialized'); /** * Names of AJAX options. * * @var array */ - var $ajaxOptions = array('type', 'confirm', 'condition', 'before', 'after', 'fallback', 'update', 'loading', 'loaded', 'interactive', 'complete', 'with', 'url', 'method', 'position', 'form', 'parameters', 'evalScripts', 'asynchronous', 'onComplete', 'onUninitialized', 'onLoading', 'onLoaded', 'onInteractive', 'success', 'failure', 'onSuccess', 'onFailure', 'insertion', 'requestHeaders', 'indicator'); + var $ajaxOptions = array('after', 'asynchronous', 'before', 'confirm', 'condition', 'contentType', 'encoding', 'evalScripts', 'failure', 'fallback', 'form', 'indicator', 'insertion', 'interactive', 'loaded', 'loading', 'method', 'onCreate', 'onComplete', 'onException', 'onFailure', 'onInteractive', 'onLoaded', 'onLoading', 'onSuccess', 'onUninitialized', 'parameters', 'position', 'postBody', 'requestHeaders', 'success', 'type', 'update', 'url', 'with'); /** * Options for draggable. * @@ -77,7 +86,7 @@ class AjaxHelper extends AppHelper { * * @var array */ - var $sortOptions = array('tag', 'only', 'overlap', 'constraint', 'containment', 'handle', 'hoverclass', 'ghosting', 'dropOnEmpty', 'scroll', 'scrollSensitivity', 'scrollSpeed', 'tree', 'treeTag', 'onUpdate', 'onChange', 'update'); + var $sortOptions = array('constraint', 'containment', 'dropOnEmpty', 'ghosting', 'handle', 'hoverclass', 'onUpdate', 'onChange', 'only', 'overlap', 'scroll', 'scrollSensitivity', 'scrollSpeed', 'tag', 'tree', 'treeTag', 'update'); /** * Options for slider. * @@ -214,7 +223,6 @@ class AjaxHelper extends AppHelper { * @return string html code for link to remote action */ function remoteFunction($options = null) { - if (isset($options['update'])) { if (!is_array($options['update'])) { $func = "new Ajax.Updater('{$options['update']}',"; @@ -253,7 +261,6 @@ class AjaxHelper extends AppHelper { } return $func; } - /** * Periodically call remote url via AJAX. * @@ -269,7 +276,6 @@ class AjaxHelper extends AppHelper { $code = "new PeriodicalExecuter(function() {" . $this->remoteFunction($options) . "}, $frequency)"; return $this->Javascript->codeBlock($code); } - /** * Returns form tag that will submit using Ajax. * @@ -325,7 +331,6 @@ class AjaxHelper extends AppHelper { return $this->Form->create($options['model'], $htmlOptions) . $this->Javascript->event("'" . $htmlOptions['id']. "'", 'submit', $this->remoteFunction($options)); } - /** * Returns a button input tag that will submit using Ajax * @@ -351,7 +356,6 @@ class AjaxHelper extends AppHelper { return $this->Form->submit($title, $htmlOptions) . $this->Javascript->event('"' . $htmlOptions['id'] . '"', 'click', $this->remoteFunction($options)); } - /** * Observe field and call ajax on change. * @@ -391,7 +395,6 @@ class AjaxHelper extends AppHelper { } return $this->Javascript->codeBlock($this->_buildObserver('Form.Element.' . $observer . 'Observer', $field_id, $options)); } - /** * Observe entire form and call ajax on change. * @@ -410,7 +413,6 @@ class AjaxHelper extends AppHelper { } return $this->Javascript->codeBlock($this->_buildObserver('Form.Observer', $field_id, $options)); } - /** * Create a text field with Autocomplete. * @@ -425,7 +427,6 @@ class AjaxHelper extends AppHelper { * @return string Ajax script */ function autoComplete($field, $url = "", $options = array()) { - $var = ''; if (isset($options['var'])) { $var = 'var ' . $options['var'] . ' = '; @@ -560,7 +561,6 @@ class AjaxHelper extends AppHelper { $options = $this->_buildOptions($options, $this->dropOptions); return $this->Javascript->codeBlock("Droppables.add('{$id}', {$options});"); } - /** * Makes a slider control. * @@ -650,6 +650,7 @@ class AjaxHelper extends AppHelper { } $options = $this->_optionsToString($options, array('tag', 'constraint', 'only', 'handle', 'hoverclass', 'scroll', 'tree', 'treeTag', 'update')); + $options = array_merge($options, $this->_buildCallbacks($options)); $options = $this->_buildOptions($options, $this->sortOptions); return $this->Javascript->codeBlock("Sortable.create('$id', $options);"); } @@ -658,15 +659,14 @@ class AjaxHelper extends AppHelper { * */ function __optionsForAjax($options = array()) { - if (isset($options['indicator'])) { if (isset($options['loading'])) { if (!empty($options['loading']) && substr(trim($options['loading']), -1, 1) != ';') { $options['loading'] .= '; '; } - $options['loading'] .= "Element.show('{$options['indicator']}');"; + $options['loading'] .= "Element.show('{$options['indicator']}');"; } else { - $options['loading'] = "Element.show('{$options['indicator']}');"; + $options['loading'] = "Element.show('{$options['indicator']}');"; } if (isset($options['complete'])) { if (!empty($options['complete']) && substr(trim($options['complete']), -1, 1) != ';') { @@ -674,7 +674,7 @@ class AjaxHelper extends AppHelper { } $options['complete'] .= "Element.hide('{$options['indicator']}');"; } else { - $options['complete'] = "Element.hide('{$options['indicator']}');"; + $options['complete'] = "Element.hide('{$options['indicator']}');"; } unset($options['indicator']); } @@ -683,7 +683,8 @@ class AjaxHelper extends AppHelper { array('asynchronous' => 'true', 'evalScripts' => 'true'), $this->_buildCallbacks($options) ); - $options = $this->_optionsToString($options, array('method')); + $options = $this->_optionsToString($options, array('contentType', 'encoding', 'fallback', 'method', 'postBody', 'update', 'url')); + $jsOptions = array_merge($jsOptions, array_intersect_key($options, array_flip(array('contentType', 'encoding', 'method', 'postBody')))); foreach ($options as $key => $value) { switch($key) { @@ -738,7 +739,6 @@ class AjaxHelper extends AppHelper { return $options; } - /** * Returns a string of JavaScript with the given option data as a JavaScript options hash. * @@ -768,7 +768,6 @@ class AjaxHelper extends AppHelper { return false; } } - /** * Return JavaScript text for an observer... * @@ -801,10 +800,19 @@ class AjaxHelper extends AppHelper { if (isset($options[$callback])) { $name = 'on' . ucfirst($callback); $code = $options[$callback]; - if ($name == 'onComplete') { - $callbacks[$name] = "function(request, json) {" . $code . "}"; - } else { - $callbacks[$name] = "function(request) {" . $code . "}"; + switch($name) { + case 'onComplete': + $callbacks[$name] = "function(request, json) {" . $code . "}"; + break; + case 'onCreate': + $callbacks[$name] = "function(request, xhr) {" . $code . "}"; + break; + case 'onException': + $callbacks[$name] = "function(request, exception) {" . $code . "}"; + break; + default: + $callbacks[$name] = "function(request) {" . $code . "}"; + break; } if (isset($options['bind'])) { if ((is_array($options['bind']) && in_array($callback, $options['bind'])) || (is_string($options['bind']) && strpos($options['bind'], $callback) !== false)) { @@ -825,7 +833,7 @@ class AjaxHelper extends AppHelper { */ function _optionsToString($options, $stringOpts = array()) { foreach ($stringOpts as $option) { - if (isset($options[$option]) && $options[$option][0] != "'") { + if (isset($options[$option]) && !empty($options[$option]) && is_string($options[$option]) && $options[$option][0] != "'") { if ($options[$option] === true || $options[$option] === 'true') { $options[$option] = 'true'; } elseif ($options[$option] === false || $options[$option] === 'false') { @@ -837,9 +845,14 @@ class AjaxHelper extends AppHelper { } return $options; } - +/** + * Executed after a view has rendered, used to include bufferred code + * blocks. + * + * @access public + */ function afterRender() { - if (env('HTTP_X_UPDATE') != null && count($this->__ajaxBuffer) > 0) { + if (env('HTTP_X_UPDATE') != null && !empty($this->__ajaxBuffer)) { @ob_end_clean(); $data = array(); diff --git a/cake/tests/cases/libs/view/helpers/ajax.test.php b/cake/tests/cases/libs/view/helpers/ajax.test.php index 4b90e4d30..e45c6652e 100644 --- a/cake/tests/cases/libs/view/helpers/ajax.test.php +++ b/cake/tests/cases/libs/view/helpers/ajax.test.php @@ -61,26 +61,26 @@ class PostAjaxTest extends Model { class AjaxTest extends UnitTestCase { function setUp() { Router::reload(); - $this->Ajax = new AjaxHelper(); - $this->Ajax->Html = new HtmlHelper(); - $this->Ajax->Form = new FormHelper(); - $this->Ajax->Javascript = new JavascriptHelper(); + $this->Ajax =& new AjaxHelper(); + $this->Ajax->Html =& new HtmlHelper(); + $this->Ajax->Form =& new FormHelper(); + $this->Ajax->Javascript =& new JavascriptHelper(); $this->Ajax->Form->Html =& $this->Ajax->Html; - $view = new View(new AjaxTestController()); + $view =& new View(new AjaxTestController()); ClassRegistry::addObject('view', $view); ClassRegistry::addObject('PostAjaxTest', new PostAjaxTest()); } function testEvalScripts() { - $result = $this->Ajax->link('Test Link', '/', array('id' => 'link1', 'update' => 'content', 'evalScripts' => false)); - $this->assertPattern('/^]+>Test Link<\/a>]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); - $this->assertPattern('/^]*href="\/"[^<>]*>/', $result); + $result = $this->Ajax->link('Test Link', 'http://www.cakephp.org', array('id' => 'link1', 'update' => 'content', 'evalScripts' => false)); + $this->assertPattern('/^]+>Test Link<\/a>]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); + $this->assertPattern('/^]*href="http:\/\/www.cakephp.org"[^<>]*>/', $result); $this->assertPattern('/^]*id="link1"[^<>]*>/', $result); $this->assertPattern('/^]*onclick="\s*' . str_replace('/', '\\/', preg_quote('event.returnValue = false; return false;')) . '\s*"[^<>]*>/', $result); - $result = $this->Ajax->link('Test Link', '/', array('id' => 'link1', 'update' => 'content')); - $this->assertPattern('/^]+>Test Link<\/a>]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); - $this->assertPattern('/^]*href="\/"[^<>]*>/', $result); + $result = $this->Ajax->link('Test Link', 'http://www.cakephp.org', array('id' => 'link1', 'update' => 'content')); + $this->assertPattern('/^]+>Test Link<\/a>]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); + $this->assertPattern('/^]*href="http:\/\/www.cakephp.org"[^<>]*>/', $result); $this->assertPattern('/^]*id="link1"[^<>]*>/', $result); $this->assertPattern('/^]*onclick="\s*' . str_replace('/', '\\/', preg_quote('event.returnValue = false; return false;')) . '\s*"[^<>]*>/', $result); } @@ -134,26 +134,22 @@ class AjaxTest extends UnitTestCase { $expected = 'Sortable.create(\'ull\', {constraint:false, ghosting:true});'; $this->assertPattern('/^]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); - $result = $this->Ajax->sortable('ull', array('constraint'=>'false', 'ghosting'=>'true', 'complete' => 'dummy();')); - $expected = 'Sortable.create(\'ull\', {constraint:false, ghosting:true});'; - $this->assertPattern('/^]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); - $result = $this->Ajax->sortable('ull', array('constraint'=>'false', 'ghosting'=>'true', 'update' => 'myId')); $expected = 'Sortable.create(\'ull\', {constraint:false, ghosting:true, update:\'myId\'});'; $this->assertPattern('/^]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); - $result = $this->Ajax->sortable('faqs', array('url'=>'/admin/faqs/order', + $result = $this->Ajax->sortable('faqs', array('url'=>'http://www.cakephp.org', 'update' => 'faqs', 'tag' => 'tbody', 'handle' => 'grip', 'before' => 'Element.hide(\'message\')', 'complete' => 'Element.show(\'message\');')); - $expected = 'Sortable.create(\'faqs\', {update:\'faqs\', tag:\'tbody\', handle:\'grip\', onUpdate:function(sortable) {Element.hide(\'message\'); new Ajax.Updater(\'faqs\',\'/admin/faqs/order\', {asynchronous:true, evalScripts:true, onComplete:function(request, json) {Element.show(\'message\');}, parameters:Sortable.serialize(\'faqs\'), requestHeaders:[\'X-Update\', \'faqs\']})}});'; + $expected = 'Sortable.create(\'faqs\', {update:\'faqs\', tag:\'tbody\', handle:\'grip\', onUpdate:function(sortable) {Element.hide(\'message\'); new Ajax.Updater(\'faqs\',\'http://www.cakephp.org\', {asynchronous:true, evalScripts:true, onComplete:function(request, json) {Element.show(\'message\');}, parameters:Sortable.serialize(\'faqs\'), requestHeaders:[\'X-Update\', \'faqs\']})}});'; $this->assertPattern('/^]+>\s*' . str_replace('/', '\\/', preg_quote('//')) . '\s*<\/script>$/', $result); } function testSubmitWithIndicator() { - $result = $this->Ajax->submit('Add', array('div' => false, 'url' => "/controller/action", 'indicator' => 'loading', 'loading' => "doSomething()", 'complete' => 'doSomethingElse() ')); + $result = $this->Ajax->submit('Add', array('div' => false, 'url' => "http://www.cakephp.org", 'indicator' => 'loading', 'loading' => "doSomething()", 'complete' => 'doSomethingElse() ')); $this->assertPattern('/onLoading:function\(request\) {doSomething\(\);\s+Element.show\(\'loading\'\);}/', $result); $this->assertPattern('/onComplete:function\(request, json\) {doSomethingElse\(\) ;\s+Element.hide\(\'loading\'\);}/', $result); } @@ -217,7 +213,84 @@ class AjaxTest extends UnitTestCase { $this->assertPattern('/]+type="text\/javascript"[^<>]*>/', $result); $this->assertNoPattern('/]+[^type]=[^<>]*>/', $result); $this->assertPattern('/Event.observe\(\'myLink\',\s*\'click\',\s*function\(event\)\s*{.+},\s*false\);\s*' . str_replace('/', '\\/', preg_quote('//]]>')) . '\s*<\/script>$/', $result); - $this->assertPattern('/function\(event\)\s*{\s*new Ajax\.Updater\(\'myDiv\',\s*\'http:\/\/www.cakephp.org\/downloads\',\s*{asynchronous:true, evalScripts:true, onLoading:function\(request\) {myLoading\(\);}, onComplete:function\(request, json\) {myComplete\(\);}, requestHeaders:\[\'X-Update\', \'myDiv\'\]}\)\s*},\s*false\);/', $result); + $this->assertPattern('/function\(event\)\s*{\s*new Ajax\.Updater\(\'myDiv\',\s*\'http:\/\/www.cakephp.org\/downloads\',\s*{asynchronous:true, evalScripts:true, onComplete:function\(request, json\) {myComplete\(\);}, onLoading:function\(request\) {myLoading\(\);}, requestHeaders:\[\'X-Update\', \'myDiv\'\]}\)\s*},\s*false\);/', $result); + + $result = $this->Ajax->link('Ajax Link', 'http://www.cakephp.org/downloads', array('update' => 'myDiv', 'encoding' => 'utf-8')); + $this->assertPattern('/^]+>Ajax Link<\/a>