summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes2
-rw-r--r--framework/Web/Javascripts/js/ajax.js18
-rw-r--r--framework/Web/Javascripts/js/prado.js10
-rw-r--r--framework/Web/Javascripts/prado/ajax3.js49
-rw-r--r--framework/Web/Javascripts/prado/element.js12
-rw-r--r--framework/Web/UI/ActiveControls/TActiveControl.php125
-rw-r--r--framework/Web/UI/ActiveControls/TActivePageAdapter.php33
-rw-r--r--framework/Web/UI/ActiveControls/TCallback.php165
-rw-r--r--framework/Web/UI/ActiveControls/TCallbackClientScript.php109
-rw-r--r--framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php68
-rw-r--r--framework/Web/UI/ActiveControls/TCallbackResponse.php45
-rw-r--r--framework/Web/UI/TControl.php7
-rw-r--r--framework/Web/UI/THtmlWriter.php9
-rw-r--r--framework/Web/UI/TPage.php2
-rw-r--r--tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page21
-rw-r--r--tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.php15
16 files changed, 571 insertions, 119 deletions
diff --git a/.gitattributes b/.gitattributes
index 58bdd50b..3e3e7b27 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -930,6 +930,7 @@ framework/Web/THttpUtility.php -text
framework/Web/UI/ActiveControls/TActiveControl.php -text
framework/Web/UI/ActiveControls/TActiveControlAdapter.php -text
framework/Web/UI/ActiveControls/TActivePageAdapter.php -text
+framework/Web/UI/ActiveControls/TCallback.php -text
framework/Web/UI/ActiveControls/TCallbackClientScript.php -text
framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php -text
framework/Web/UI/ActiveControls/TCallbackResponse.php -text
@@ -1035,6 +1036,7 @@ tests/FunctionalTests/features/protected/controls/LabeledTextbox.php -text
tests/FunctionalTests/features/protected/controls/Layout.php -text
tests/FunctionalTests/features/protected/controls/Layout.tpl -text
tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page -text
+tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.php -text
tests/FunctionalTests/features/protected/pages/ActiveControls/config.xml -text
tests/FunctionalTests/features/protected/pages/ColorPicker.page -text
tests/FunctionalTests/features/protected/pages/CompositeControl.page -text
diff --git a/framework/Web/Javascripts/js/ajax.js b/framework/Web/Javascripts/js/ajax.js
index 6ae737bf..375d90e9 100644
--- a/framework/Web/Javascripts/js/ajax.js
+++ b/framework/Web/Javascripts/js/ajax.js
@@ -22,7 +22,8 @@ if(this.responseIsSuccess()){if(this.onComplete)
setTimeout(this.onComplete.bind(this),10);}}});Ajax.PeriodicalUpdater=Class.create();Ajax.PeriodicalUpdater.prototype=Object.extend(new Ajax.Base(),{initialize:function(container,url,options){this.setOptions(options);this.onComplete=this.options.onComplete;this.frequency=(this.options.frequency||2);this.decay=(this.options.decay||1);this.updater={};this.container=container;this.url=url;this.start();},start:function(){this.options.onComplete=this.updateComplete.bind(this);this.onTimerEvent();},stop:function(){this.updater.onComplete=undefined;clearTimeout(this.timer);(this.onComplete||Prototype.emptyFunction).apply(this,arguments);},updateComplete:function(request){if(this.options.decay){this.decay=(request.responseText==this.lastText?this.decay*this.options.decay:1);this.lastText=request.responseText;}
this.timer=setTimeout(this.onTimerEvent.bind(this),this.decay*this.frequency*1000);},onTimerEvent:function(){this.updater=new Ajax.Updater(this.container,this.url,this.options);}});Object.extend(Ajax.Request.prototype,{respondToReadyState:function(readyState)
{var event=Ajax.Request.Events[readyState];var transport=this.transport,json=this.getHeaderData(Prado.CallbackRequest.DATA_HEADER);if(event=='Complete')
-{Ajax.Responders.dispatch('on'+transport.status,this,transport,json);Prado.CallbackRequest.dispatchActions(this.getHeaderData(Prado.CallbackRequest.ACTION_HEADER));try{(this.options['on'+this.transport.status]||this.options['on'+(this.responseIsSuccess()?'Success':'Failure')]||Prototype.emptyFunction)(transport,json);}catch(e){this.dispatchException(e);}
+{try
+{Ajax.Responders.dispatch('on'+transport.status,this,transport,json);Prado.CallbackRequest.dispatchActions(transport,this.getHeaderData(Prado.CallbackRequest.ACTION_HEADER));(this.options['on'+this.transport.status]||this.options['on'+(this.responseIsSuccess()?'Success':'Failure')]||Prototype.emptyFunction)(transport,json);}catch(e){this.dispatchException(e);}
if((this.header('Content-type')||'').match(/^text\/javascript/i))
this.evalResponse();}
try{(this.options['on'+event]||Prototype.emptyFunction)(transport,json);Ajax.Responders.dispatch('on'+event,this,transport,json);}catch(e){this.dispatchException(e);}
@@ -33,12 +34,13 @@ this.transport.onreadystatechange=Prototype.emptyFunction;},getHeaderData:functi
catch(e)
{if(typeof(json)=="string")
{Logger.info("using json")
-return Prado.CallbackRequest.decode(json);}}}});Prado.CallbackRequest=Class.create();Object.extend(Prado.CallbackRequest,{FIELD_CALLBACK_TARGET:'PRADO_CALLBACK_TARGET',FIELD_CALLBACK_PARAMETER:'PRADO_CALLBACK_PARAMETER',PostDataLoaders:['PRADO_PAGESTATE'],DATA_HEADER:'X-PRADO-DATA',ACTION_HEADER:'X-PRADO-ACTIONS',ERROR_HEADER:'X-PRADO-ERROR',dispatchActions:function(actions)
-{actions.each(this.__run);},__run:function(command)
+return Prado.CallbackRequest.decode(json);}}}});Prado.CallbackRequest=Class.create();Object.extend(Prado.CallbackRequest,{FIELD_CALLBACK_TARGET:'PRADO_CALLBACK_TARGET',FIELD_CALLBACK_PARAMETER:'PRADO_CALLBACK_PARAMETER',FIELD_CALLBACK_PAGESTATE:'PRADO_PAGESTATE',PostDataLoaders:[],DATA_HEADER:'X-PRADO-DATA',ACTION_HEADER:'X-PRADO-ACTIONS',ERROR_HEADER:'X-PRADO-ERROR',dispatchActions:function(transport,actions)
+{if(actions&&actions.length>0)
+actions.each(this.__run.bind(this,transport));},__run:function(transport,command)
{for(var method in command)
{if(command[method][0])
{var id=command[method][0];if($(id)||id.indexOf("[]")>-1)
-method.toFunction().apply(this,command[method]);else if(typeof(Logger)!="undefined")
+method.toFunction().apply(this,command[method].concat(transport));else if(typeof(Logger)!="undefined")
{Logger.error("Error in executing callback response:","Unable to find HTML element with ID '"+id+"' before executing "+method+"().");}}}},Exception:{"on505":function(request,transport,data)
{var e=request.getHeaderData(Prado.CallbackRequest.ERROR_HEADER);Logger.error("Callback Server Error "+e.code,this.formatException(e));},'on200':function(request,transport,data)
{if(transport.status<500)
@@ -59,12 +61,14 @@ Object.extend(this.options||{},request);if(this.options.CausesValidation!=false&
{var form=this.options.Form||Prado.Validation.getForm();if(Prado.Validation.validate(form,this.options.ValidationGroup,this)==false)
return;}
this.request=new Ajax.Request(this.url,this.options);},_getPostData:function()
-{var data={};Prado.CallbackRequest.PostDataLoaders.each(function(name)
+{var data={};var callback=Prado.CallbackRequest;if(this.options.PostState!=false)
+{callback.PostDataLoaders.each(function(name)
{$A(document.getElementsByName(name)).each(function(element)
{var value=$F(element);if(typeof(value)!="undefined")
-data[name]=value;})})
+data[name]=value;})})}
if(typeof(this.options.params)!="undefined")
-data[Prado.CallbackRequest.FIELD_CALLBACK_PARAMETER]=Prado.CallbackRequest.encode(this.options.params);data[Prado.CallbackRequest.FIELD_CALLBACK_TARGET]=this.id;return $H(data).toQueryString();}}
+data[callback.FIELD_CALLBACK_PARAMETER]=callback.encode(this.options.params);var pageState=$F(callback.FIELD_CALLBACK_PAGESTATE);if(typeof(pageState)!="undefined")
+data[callback.FIELD_CALLBACK_PAGESTATE]=pageState;data[callback.FIELD_CALLBACK_TARGET]=this.id;return $H(data).toQueryString();}}
Prado.Callback=function(UniqueID,parameter,onSuccess,options)
{var callback={'params':parameter||'','onSuccess':onSuccess||Prototype.emptyFunction,'CausesValidation':true};Object.extend(callback,options||{});new Prado.CallbackRequest(UniqueID,callback);return false;}
Array.prototype.______array='______array';Prado.JSON={org:'http://www.JSON.org',copyright:'(c)2005 JSON.org',license:'http://www.crockford.com/JSON/license.html',stringify:function(arg){var c,i,l,s='',v;switch(typeof arg){case'object':if(arg){if(arg.______array=='______array'){for(i=0;i<arg.length;++i){v=this.stringify(arg[i]);if(s){s+=',';}
diff --git a/framework/Web/Javascripts/js/prado.js b/framework/Web/Javascripts/js/prado.js
index d80f984d..f6c52983 100644
--- a/framework/Web/Javascripts/js/prado.js
+++ b/framework/Web/Javascripts/js/prado.js
@@ -256,7 +256,7 @@ selection[method](isList?element:el,value);},click:function(element)
{var el=$(element);if(!el)return;if(document.createEvent)
{var evt=document.createEvent('HTMLEvents');evt.initEvent('click',true,true);el.dispatchEvent(evt);}
else if(el.fireEvent)
-{el.fireEvent('onclick');if(isFunction(el.onclick))
+{el.fireEvent('onclick');if(typeof(el.onclick)=="function")
el.onclick();}},setAttribute:function(element,attribute,value)
{var el=$(element);if(attribute=="disabled"&&value==false)
el.removeAttribute(attribute);else
@@ -265,8 +265,12 @@ el.setAttribute(attribute,value);},setOptions:function(element,options)
{while(el.length>0)
el.remove(0);for(var i=0;i<options.length;i++)
el.options[el.options.length]=new Option(options[i][0],options[i][1]);}},focus:function(element)
-{var obj=$(element);if(isObject(obj)&&isdef(obj.focus))
-setTimeout(function(){obj.focus();},100);return false;}}
+{var obj=$(element);if(typeof(obj)!="undefined"&&typeof(obj.focus)!="undefined")
+setTimeout(function(){obj.focus();},100);return false;},replaceContent:function(element,method,content,boundary,transport)
+{if(boundary)
+{var f=RegExp('(<!--'+boundary+'-->)([\\s\\S\\w\\W]*)(<!--//'+boundary+'-->)',"m");var result=transport.responseText.match(f);if(result&&result.length>=2)
+content=result[2];}
+method.toFunction().apply(this,[element,content]);}}
Prado.Element.Selection={inputValue:function(el,value)
{switch(el.type.toLowerCase())
{case'checkbox':case'radio':return el.checked=value;}},selectValue:function(el,value)
diff --git a/framework/Web/Javascripts/prado/ajax3.js b/framework/Web/Javascripts/prado/ajax3.js
index 1cffbd73..63add490 100644
--- a/framework/Web/Javascripts/prado/ajax3.js
+++ b/framework/Web/Javascripts/prado/ajax3.js
@@ -13,11 +13,13 @@ Object.extend(Ajax.Request.prototype,
var transport = this.transport, json = this.getHeaderData(Prado.CallbackRequest.DATA_HEADER);
if (event == 'Complete')
- {
+ {
+ try
+ {
Ajax.Responders.dispatch('on' + transport.status, this, transport, json);
- Prado.CallbackRequest.dispatchActions(this.getHeaderData(Prado.CallbackRequest.ACTION_HEADER));
-
- try {
+ Prado.CallbackRequest.dispatchActions(transport,this.getHeaderData(Prado.CallbackRequest.ACTION_HEADER));
+
+
(this.options['on' + this.transport.status]
|| this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(transport, json);
@@ -82,9 +84,13 @@ Object.extend(Prado.CallbackRequest,
*/
FIELD_CALLBACK_PARAMETER : 'PRADO_CALLBACK_PARAMETER',
/**
+ * Callback request page state field name,
+ */
+ FIELD_CALLBACK_PAGESTATE : 'PRADO_PAGESTATE',
+ /**
* List of form fields that will be collected during callback.
*/
- PostDataLoaders : ['PRADO_PAGESTATE'],
+ PostDataLoaders : [],
/**
* Response data header name.
*/
@@ -101,15 +107,16 @@ Object.extend(Prado.CallbackRequest,
/**
* Dispatch callback response actions.
*/
- dispatchActions : function(actions)
+ dispatchActions : function(transport,actions)
{
- actions.each(this.__run);
+ if(actions && actions.length > 0)
+ actions.each(this.__run.bind(this,transport));
},
/**
* Prase and evaluate a Callback clien-side action
*/
- __run : function(command)
+ __run : function(transport, command)
{
for(var method in command)
{
@@ -117,7 +124,7 @@ Object.extend(Prado.CallbackRequest,
{
var id = command[method][0];
if($(id) || id.indexOf("[]") > -1)
- method.toFunction().apply(this,command[method]);
+ method.toFunction().apply(this,command[method].concat(transport));
else if(typeof(Logger) != "undefined")
{
Logger.error("Error in executing callback response:",
@@ -269,19 +276,25 @@ Prado.CallbackRequest.prototype =
_getPostData : function()
{
var data = {};
-
- Prado.CallbackRequest.PostDataLoaders.each(function(name)
+ var callback = Prado.CallbackRequest;
+ if(this.options.PostState != false)
{
- $A(document.getElementsByName(name)).each(function(element)
+ callback.PostDataLoaders.each(function(name)
{
- var value = $F(element);
- if(typeof(value) != "undefined")
- data[name] = value;
+ $A(document.getElementsByName(name)).each(function(element)
+ {
+ var value = $F(element);
+ if(typeof(value) != "undefined")
+ data[name] = value;
+ })
})
- })
+ }
if(typeof(this.options.params) != "undefined")
- data[Prado.CallbackRequest.FIELD_CALLBACK_PARAMETER] = Prado.CallbackRequest.encode(this.options.params);
- data[Prado.CallbackRequest.FIELD_CALLBACK_TARGET] = this.id;
+ data[callback.FIELD_CALLBACK_PARAMETER] = callback.encode(this.options.params);
+ var pageState = $F(callback.FIELD_CALLBACK_PAGESTATE);
+ if(typeof(pageState) != "undefined")
+ data[callback.FIELD_CALLBACK_PAGESTATE] = pageState;
+ data[callback.FIELD_CALLBACK_TARGET] = this.id;
return $H(data).toQueryString();
}
}
diff --git a/framework/Web/Javascripts/prado/element.js b/framework/Web/Javascripts/prado/element.js
index 06937253..88fb9ec5 100644
--- a/framework/Web/Javascripts/prado/element.js
+++ b/framework/Web/Javascripts/prado/element.js
@@ -74,6 +74,18 @@ Prado.Element =
if(typeof(obj) != "undefined" && typeof(obj.focus) != "undefined")
setTimeout(function(){ obj.focus(); }, 100);
return false;
+ },
+
+ replaceContent : function(element, method, content, boundary, transport)
+ {
+ if(boundary)
+ {
+ var f = RegExp('(<!--'+boundary+'-->)([\\s\\S\\w\\W]*)(<!--//'+boundary+'-->)',"m");
+ var result = transport.responseText.match(f);
+ if(result && result.length >= 2)
+ content = result[2];
+ }
+ method.toFunction().apply(this,[element,content]);
}
}
diff --git a/framework/Web/UI/ActiveControls/TActiveControl.php b/framework/Web/UI/ActiveControls/TActiveControl.php
index e61682d3..62470a18 100644
--- a/framework/Web/UI/ActiveControls/TActiveControl.php
+++ b/framework/Web/UI/ActiveControls/TActiveControl.php
@@ -6,14 +6,65 @@
class TActiveControl extends TControl implements ICallbackEventHandler, IActiveControl
{
+ /**
+ * @var TCallbackClientSideOptions client-side options.
+ */
private $_clientSide;
+ /**
+ * Creates a new callback control, sets the adapter to
+ * TActiveControlAdapter. If you override this class, be sure to set the
+ * adapter appropriately by, for example, call this constructor.
+ */
public function __construct()
{
parent::__construct();
$this->setAdapter(new TActiveControlAdapter($this));
}
+ /**
+ * @return boolean whether callback event trigger by this button will cause
+ * input validation, default is true
+ */
+ public function getCausesValidation()
+ {
+ return $this->getViewState('CausesValidation',true);
+ }
+
+ /**
+ * @param boolean whether callback event trigger by this button will cause
+ * input validation
+ */
+ public function setCausesValidation($value)
+ {
+ $this->setViewState('CausesValidation',TPropertyValue::ensureBoolean($value),true);
+ }
+
+ /**
+ * @return string the group of validators which the button causes validation
+ * upon callback
+ */
+ public function getValidationGroup()
+ {
+ return $this->getViewState('ValidationGroup','');
+ }
+
+ /**
+ * @param string the group of validators which the button causes validation
+ * upon callback
+ */
+ public function setValidationGroup($value)
+ {
+ $this->setViewState('ValidationGroup',$value,'');
+ }
+
+ /**
+ * Callback client-side options can be set by setting the properties of
+ * the ClientSide property. E.g. <com:TCallback ClientSide.OnSuccess="..." />
+ * See {@link TCallbackClientSideOptions} for details on the properties of
+ * ClientSide.
+ * @return TCallbackClientSideOptions client-side callback options.
+ */
public function getClientSide()
{
if(is_null($this->_clientSide))
@@ -21,28 +72,78 @@ class TActiveControl extends TControl implements ICallbackEventHandler, IActiveC
return $this->_clientSide;
}
+ /**
+ * @return TCallbackClientSideOptions callback client-side options.
+ */
protected function createClientSideOptions()
{
- $client = new TCallbackClientSideOptions;
- return $client;
+ return new TCallbackClientSideOptions;
}
+
+ /**
+ * @return boolean whether to perform validation if the callback is
+ * requested.
+ */
+ protected function canCauseValidation()
+ {
+ if($this->getCausesValidation())
+ {
+ $group=$this->getValidationGroup();
+ return $this->getPage()->getValidators($group)->getCount()>0;
+ }
+ else
+ return false;
+ }
+ /**
+ * Raises the callback event. This method is required by {@link
+ * ICallbackEventHandler} interface. If {@link getCausesValidation
+ * CausesValidation} is true, it will invoke the page's {@link TPage::
+ * validate validate} method first. It will raise {@link onCallback
+ * OnCallback} event. This method is mainly used by framework and control
+ * developers.
+ * @param TCallbackEventParameter the event parameter
+ */
public function raiseCallbackEvent($param)
{
- var_dump($param->getParameter());
- $param->Output->write($param->Parameter);
- $client = $this->getPage()->getCallbackClient();
- $client->hide($this);
- $client->toggle($this);
- $client->update($this, 1);
- $param->setData(array("asdasdad",1));
+ if($this->getCausesValidation())
+ $this->getPage()->validate($this->getValidationGroup());
+ $this->onCallback($param);
+ }
+
+ /**
+ * This method is invoked when a callback is requested. The method raises
+ * 'OnCallback' event to fire up the event handlers. If you override this
+ * method, be sure to call the parent implementation so that the event
+ * handler can be invoked.
+ * @param TCallbackEventParameter event parameter to be passed to the event handlers
+ */
+ public function onCallback($param)
+ {
+ $this->raiseEvent('OnCallback', $this, $param);
}
+ /**
+ * @return array list of callback javascript options.
+ */
+ protected function getCallbackOptions()
+ {
+ $validate = $this->getCausesValidation();
+ $options['CausesValidation']= $validate ? '' : false;
+ $options['ValidationGroup']=$this->getValidationGroup();
+ return $options;
+ }
+
+ /**
+ * Returns the javascript statement to invoke a callback request for this
+ * control. Additional options for callback can be set via subproperties of
+ * {@link getClientSide ClientSide} property. E.g. ClientSide.OnSuccess="..."
+ * @return string javascript statement to invoke a callback.
+ */
public function getCallbackReference()
{
- // $formID = $this->getPage()->getForm()->getClientID();
- // $this->getClientSide()->setValidationForm($formID);
- return $this->getPage()->getClientScript()->getCallbackReference($this);
+ $client = $this->getPage()->getClientScript();
+ return $client->getCallbackReference($this, $this->getCallbackOptions());
}
}
diff --git a/framework/Web/UI/ActiveControls/TActivePageAdapter.php b/framework/Web/UI/ActiveControls/TActivePageAdapter.php
index 2607fec2..4cb785dd 100644
--- a/framework/Web/UI/ActiveControls/TActivePageAdapter.php
+++ b/framework/Web/UI/ActiveControls/TActivePageAdapter.php
@@ -50,7 +50,7 @@ class TActivePageAdapter extends TControlAdapter
public function __construct(TPage $control)
{
parent::__construct($control);
- //$this->getApplication()->setResponse($this->getCallbackResponseHandler());
+ $this->getApplication()->setResponse($this->createCallbackResponseHandler());
$this->trapCallbackErrorsExceptions();
}
@@ -75,6 +75,7 @@ class TActivePageAdapter extends TControlAdapter
{
Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
$this->renderResponse($writer);
+ //$this->getResponse()->flush();
}
/**
@@ -87,9 +88,11 @@ class TActivePageAdapter extends TControlAdapter
$executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute()->toArray();
$actions = TJavascript::jsonEncode($executeJavascript);
$response->appendHeader(self::CALLBACK_ACTION_HEADER.': '.$actions);
- $data = TJavascript::jsonEncode($this->_result->getData());
- $response->appendHeader(self::CALLBACK_DATA_HEADER.': '.$data);
- $response->flush();
+ if($this->_result)
+ {
+ $data = TJavascript::jsonEncode($this->_result->getData());
+ $response->appendHeader(self::CALLBACK_DATA_HEADER.': '.$data);
+ }
}
/**
@@ -104,12 +107,14 @@ class TActivePageAdapter extends TControlAdapter
{
if($callbackHandler instanceof ICallbackEventHandler)
{
- $writer = $this->getResponse()->createHtmlWriter();
- $this->_result = new TCallbackEventParameter($writer, $this->getCallbackEventParameter());
+ $param = $this->getCallbackEventParameter();
+ $this->_result = new TCallbackEventParameter($this->getResponse(), $param);
$callbackHandler->raiseCallbackEvent($this->_result);
}
else
+ {
throw new TInvalidCallbackHandlerException($callbackHandler->getUniqueID());
+ }
}
else
{
@@ -193,6 +198,11 @@ class TActivePageAdapter extends TControlAdapter
$this->_callbackClient = $handler;
}
+ protected function createCallbackResponseHandler()
+ {
+ return new TCallbackResponse();
+ }
+
}
/**
@@ -213,9 +223,9 @@ class TActivePageAdapter extends TControlAdapter
class TCallbackEventParameter extends TEventParameter
{
/**
- * @var THtmlWriter output content.
+ * @var TCallbackResponse output content.
*/
- private $_output;
+ private $_response;
/**
* @var mixed callback request parameter.
*/
@@ -228,9 +238,9 @@ class TCallbackEventParameter extends TEventParameter
/**
* Creates a new TCallbackEventParameter.
*/
- public function __construct($writer, $parameter)
+ public function __construct($response, $parameter)
{
- $this->_output = $writer;
+ $this->_response = $response;
$this->_parameter = $parameter;
}
@@ -239,7 +249,7 @@ class TCallbackEventParameter extends TEventParameter
*/
public function getOutput()
{
- return $this->_output;
+ return $this->_response->createHtmlWriter();
}
/**
@@ -283,6 +293,7 @@ class TCallbackErrorHandler extends TErrorHandler
error_log("Error happened while processing an existing error:\n".$exception->__toString());
header('HTTP/1.0 500 Internal Error');
}
+ $this->getApplication()->getResponse()->flush();
}
private function getExceptionData($exception)
diff --git a/framework/Web/UI/ActiveControls/TCallback.php b/framework/Web/UI/ActiveControls/TCallback.php
new file mode 100644
index 00000000..d3b1f54d
--- /dev/null
+++ b/framework/Web/UI/ActiveControls/TCallback.php
@@ -0,0 +1,165 @@
+<?php
+
+/*
+ * Created on 25/04/2006
+ */
+
+class TCallback extends TWebControl implements ICallbackEventHandler
+{
+ /**
+ * @var TCallbackClientSideOptions client-side options.
+ */
+ private $_clientSide;
+
+ /**
+ * Creates a new callback control, sets the adapter to
+ * TActiveControlAdapter. If you override this class, be sure to set the
+ * adapter appropriately by, for example, call this constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->setAdapter(new TActiveControlAdapter($this));
+ }
+
+ /**
+ * @return string tag name of the panel
+ */
+ protected function getTagName()
+ {
+ return 'div';
+ }
+
+ /**
+ * @return boolean whether callback event trigger by this button will cause
+ * input validation, default is true
+ */
+ public function getCausesValidation()
+ {
+ return $this->getViewState('CausesValidation',true);
+ }
+
+ /**
+ * @param boolean whether callback event trigger by this button will cause
+ * input validation
+ */
+ public function setCausesValidation($value)
+ {
+ $this->setViewState('CausesValidation',TPropertyValue::ensureBoolean($value),true);
+ }
+
+ /**
+ * @return string the group of validators which the button causes validation
+ * upon callback
+ */
+ public function getValidationGroup()
+ {
+ return $this->getViewState('ValidationGroup','');
+ }
+
+ /**
+ * @param string the group of validators which the button causes validation
+ * upon callback
+ */
+ public function setValidationGroup($value)
+ {
+ $this->setViewState('ValidationGroup',$value,'');
+ }
+
+ /**
+ * Callback client-side options can be set by setting the properties of
+ * the ClientSide property. E.g. <com:TCallback ClientSide.OnSuccess="..." />
+ * See {@link TCallbackClientSideOptions} for details on the properties of
+ * ClientSide.
+ * @return TCallbackClientSideOptions client-side callback options.
+ */
+ public function getClientSide()
+ {
+ if(is_null($this->_clientSide))
+ $this->_clientSide = $this->createClientSideOptions();
+ return $this->_clientSide;
+ }
+
+ /**
+ * @return TCallbackClientSideOptions callback client-side options.
+ */
+ protected function createClientSideOptions()
+ {
+ return new TCallbackClientSideOptions;
+ }
+
+ /**
+ * @return boolean whether to perform validation if the callback is
+ * requested.
+ */
+ protected function canCauseValidation()
+ {
+ if($this->getCausesValidation())
+ {
+ $group=$this->getValidationGroup();
+ return $this->getPage()->getValidators($group)->getCount()>0;
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Raises the callback event. This method is required by {@link
+ * ICallbackEventHandler} interface. If {@link getCausesValidation
+ * CausesValidation} is true, it will invoke the page's {@link TPage::
+ * validate validate} method first. It will raise {@link onCallback
+ * OnCallback} event. This method is mainly used by framework and control
+ * developers.
+ * @param TCallbackEventParameter the event parameter
+ */
+ public function raiseCallbackEvent($param)
+ {
+ if($this->getCausesValidation())
+ $this->getPage()->validate($this->getValidationGroup());
+ $this->onCallback($param);
+ }
+
+ /**
+ * This method is invoked when a callback is requested. The method raises
+ * 'OnCallback' event to fire up the event handlers. If you override this
+ * method, be sure to call the parent implementation so that the event
+ * handler can be invoked.
+ * @param TCallbackEventParameter event parameter to be passed to the event handlers
+ */
+ public function onCallback($param)
+ {
+ $this->raiseEvent('OnCallback', $this, $param);
+ }
+
+ /**
+ * @return array list of callback javascript options.
+ */
+ protected function getCallbackOptions()
+ {
+ $validate = $this->getCausesValidation();
+ $options['CausesValidation']= $validate ? '' : false;
+ $options['ValidationGroup']=$this->getValidationGroup();
+ return $options;
+ }
+
+ /**
+ * Returns the javascript statement to invoke a callback request for this
+ * control. Additional options for callback can be set via subproperties of
+ * {@link getClientSide ClientSide} property. E.g. ClientSide.OnSuccess="..."
+ * @return string javascript statement to invoke a callback.
+ */
+ public function getCallbackReference()
+ {
+ $client = $this->getPage()->getClientScript();
+ return $client->getCallbackReference($this, $this->getCallbackOptions());
+ }
+
+ public function render($writer)
+ {
+ parent::render($writer);
+ if($this->getPage()->getIsCallback())
+ $this->getPage()->getCallbackClient()->replace($this, $writer);
+ }
+}
+
+?>
diff --git a/framework/Web/UI/ActiveControls/TCallbackClientScript.php b/framework/Web/UI/ActiveControls/TCallbackClientScript.php
index 10c0e638..aaf81380 100644
--- a/framework/Web/UI/ActiveControls/TCallbackClientScript.php
+++ b/framework/Web/UI/ActiveControls/TCallbackClientScript.php
@@ -32,7 +32,7 @@
* @package System.Web.UI.ActiveControls
* @since 3.0
*/
-class TCallbackClientScript
+class TCallbackClientScript extends TApplicationComponent
{
/**
* @var TList list of client functions to execute.
@@ -69,7 +69,7 @@ class TCallbackClientScript
if(count($params) > 0)
{
if($params[0] instanceof TControl)
- $params[0] = $params[0]->getID();
+ $params[0] = $params[0]->getClientID();
}
$this->_actions->add(array($function => $params));
}
@@ -188,22 +188,20 @@ class TCallbackClientScript
* @param TControl|string new HTML content, if content is of a TControl, the
* controls render method is called.
*/
- public function update($element, $innerHTML)
+ public function update($element, $content)
{
- if($innerHTML instanceof TControl)
- $innerHTML = $innerHTML->render();
- $this->callClientFunction('Element.update', array($element, $innerHTML));
+ $this->replace($element, $content, 'Element.update');
}
/**
* Replace the innerHTML of a content with fragements of the response body.
* @param TControl|string control element or element id
*/
- public function replaceContent($element)
+/* public function replaceContent($element)
{
$this->callClientFunction('Prado.Element.replaceContent', $element);
}
-
+*/
/**
* Add a Css class name to the element.
* @param TControl|string control element or element id
@@ -229,10 +227,10 @@ class TCallbackClientScript
* @param TControl|string control element or element id
* @param string new CssClass name for the element.
*/
- public function setCssClass($element, $cssClass)
+ /*public function setCssClass($element, $cssClass)
{
$this->callClientFunction('Prado.Element.CssClass.set', array($element, $cssClass));
- }
+ }*/
/**
* Scroll the top of the browser viewing area to the location of the
@@ -261,11 +259,9 @@ class TCallbackClientScript
* @param TControl|string HTML fragement, otherwise if TControl, its render
* method will be called.
*/
- public function insertAfter($element, $innerHTML)
+ public function insertAfter($element, $content)
{
- if($innerHTML instanceof TControl)
- $innerHTML = $innerHTML->render();
- $this->callClientFunction('Prado.Element.Insert.After', array($element, $innerHTML));
+ $this->replace($element, $content, 'Element.Insert.After');
}
/**
@@ -274,11 +270,9 @@ class TCallbackClientScript
* @param TControl|string HTML fragement, otherwise if TControl, its render
* method will be called.
*/
- public function insertBefore($element, $innerHTML)
+ public function insertBefore($element, $content)
{
- if($innerHTML instanceof TControl)
- $innerHTML = $innerHTML->render();
- $this->callClientFunction('Prado.Element.Insert.Before', array($element, $innerHTML));
+ $this->replace($element, $content, 'Element.Insert.Before');
}
/**
@@ -287,11 +281,9 @@ class TCallbackClientScript
* @param TControl|string HTML fragement, otherwise if TControl, its render
* method will be called.
*/
- public function insertBelow($element, $innerHTML)
+ public function insertBelow($element, $content)
{
- if($innerHTML instanceof TControl)
- $innerHTML = $innerHTML->render();
- $this->callClientFunction('Prado.Element.Insert.Below', array($element, $innerHTML));
+ $this->replace($element, $content, 'Element.Insert.Below');
}
/**
@@ -300,12 +292,75 @@ class TCallbackClientScript
* @param TControl|string HTML fragement, otherwise if TControl, its render
* method will be called.
*/
- public function insertAbove($element, $innerHTML)
+ public function insertAbove($element, $content)
{
- if($innerHTML instanceof TControl)
- $innerHTML = $innerHTML->render();
- $this->callClientFunction('Prado.Element.Insert.Above', array($element, $innerHTML));
- }
+ $this->replace($element, $content, 'Element.Insert.Above');
+ }
+
+ /**
+ * Replace the content of an element with new content. The new content can
+ * be a string or a TControl component. If the <tt>content</tt> parameter is
+ * a TControl component, its rendered method will be called and its contents
+ * will be used for replacement.
+ * @param TControl|string control element or HTML element id.
+ * @param TControl|string HTML fragement, otherwise it will TControl's
+ * rendered content.
+ * @param string replacement method, default is to replace the outter
+ * html content.
+ * @param string provide a custom boundary.
+ * @see insertAbout
+ * @see insertBelow
+ * @see insertBefore
+ * @see insertAfter
+ */
+ public function replace($element, $content, $method="Element.replace", $boundary=null)
+ {
+ if($content instanceof TControl)
+ {
+ $boundary = $this->getRenderedContentBoundary($content);
+ $this->callClientFunction('Prado.Element.replaceContent',
+ array($element, $method, null, $boundary));
+ }
+ else if($content instanceof THtmlWriter)
+ {
+ $boundary = $this->getResponseContentBoundary($content);
+ $this->callClientFunction('Prado.Element.replaceContent',
+ array($element, $method, null, $boundary));
+ }
+ else
+ {
+ $this->callClientFunction('Prado.Element.replaceContent',
+ array($element, $method, $content, $boundary));
+ }
+ }
+
+ /**
+ * Renders the control and return the content boundary from
+ * TCallbackResponseWriter. This method should only be used by framework
+ * component developers.
+ * @param TControl control to be rendered on callback response.
+ * @return string the boundary for which the rendered content is wrapped.
+ */
+ private function getRenderedContentBoundary($control)
+ {
+ $writer = $this->getResponse()->createHtmlWriter();
+ $control->render($writer);
+ return $writer->getWriter()->getBoundary();
+ }
+
+ /**
+ * @param THtmlWriter the writer responsible for rendering html content.
+ * @return string content boundary.
+ */
+ private function getResponseContentBoundary($html)
+ {
+ if($html instanceof THtmlWriter)
+ {
+ if($html->getWriter() instanceof TCallbackResponseWriter)
+ return $html->getWriter()->getBoundary();
+ }
+ return null;
+ }
/**
* Add a visual effect the element.
diff --git a/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php b/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php
index dea1b4e1..1e1bd52f 100644
--- a/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php
+++ b/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php
@@ -34,27 +34,35 @@ class TClientSideOptions extends TComponent
}
/**
+ * TCallbackClientSideOptions class.
+ *
* The following client side events are executing in order if the callback
* request and response are send and received successfuly.
*
- * - <b>onUninitialized</b> executed when AJAX request is uninitialized.
- * - <b>onLoading</b> executed when AJAX request is initiated
- * - <b>onLoaded</b> executed when AJAX request begins.
- * - <b>onInteractive</b> executed when AJAX request is in progress.
- * - <b>onComplete</b>executed when AJAX response returns.
+ * - <b>onUninitialized</b> executed when callback request is uninitialized.
+ * - <b>onLoading</b> executed when callback request is initiated
+ * - <b>onLoaded</b> executed when callback request begins.
+ * - <b>onInteractive</b> executed when callback request is in progress.
+ * - <b>onComplete</b>executed when callback response returns.
*
* The <tt>OnSuccess</tt> and <tt>OnFailure</tt> events are raised when the
* response is returned. A successful request/response will raise
* <tt>OnSuccess</tt> event otherwise <tt>OnFailure</tt> will be raised.
*
- * - <b>onSuccess</b> executed when AJAX request returns and is successful.
- * - <b>onFailure</b> executed when AJAX request returns and fails.
+ * - <b>onSuccess</b> executed when callback request returns and is successful.
+ * - <b>onFailure</b> executed when callback request returns and fails.
+ * - <b>onException</b> raised when callback request fails due to
+ * request/response errors.
*
- * - <b>CausesValidation</b> true to perform before callback request.
- * - <b>ValidationGroup</b> validation grouping name.
*/
class TCallbackClientSideOptions extends TClientSideOptions
{
+ /**
+ * Returns javascript statement enclosed within a javascript function.
+ * @param string javascript statement, if string begins within
+ * "javascript:" the whole string is assumed to be a function.
+ * @return string javascript statement wrapped in a javascript function
+ */
protected function ensureFunction($javascript)
{
if(TJavascript::isFunction($javascript))
@@ -175,34 +183,38 @@ class TCallbackClientSideOptions extends TClientSideOptions
$this->setFunction('onFailure', $javascript);
}
- public function getCausesValidation()
- {
- return $this->getOption('CausesValidation');
- }
-
- public function setCausesValidation($value)
- {
- $this->getOptions()->add('CausesValidation', TPropertyValue::ensureBoolean($value));
- }
-
- public function getValidationGroup()
+ /**
+ * @return string javascript code for client-side onException event
+ */
+ public function getOnException()
{
- return $this->getOption('ValidationGroup');
+ return $this->getOption('onException');
}
- public function setValidationGroup($value)
+ /**
+ * @param string javascript code for client-side onException event.
+ */
+ public function setOnException($javascript)
{
- $this->getOptions()->add('ValidationGroup', $value);
- }
+ $this->setFunction('onException', $javascript);
+ }
- public function getValidationForm()
+ /**
+ * @return boolean true to post the state on callback, default is post the
+ * state on callback.
+ */
+ public function getPostState()
{
- return $this->getOption('Form');
+ return $this->getOption('PostState');
}
- public function setValidationForm($value)
+ /**
+ * @param boolean true to post the state of the form with callback requests.
+ * Default is to post the state.
+ */
+ public function setPostState($value)
{
- $this->getOptions()->add('Form', $value);
+ $this->getOptions()->add('PostState', TPropertyValue::ensureBoolean($value));
}
}
diff --git a/framework/Web/UI/ActiveControls/TCallbackResponse.php b/framework/Web/UI/ActiveControls/TCallbackResponse.php
index a05c20c7..4a893b9a 100644
--- a/framework/Web/UI/ActiveControls/TCallbackResponse.php
+++ b/framework/Web/UI/ActiveControls/TCallbackResponse.php
@@ -11,9 +11,50 @@
class TCallbackResponse extends THttpResponse
{
+ private $_writers=array();
+ public function createHtmlWriter($type=null)
+ {
+ $writer = new TCallbackResponseWriter();
+ $this->_writers[] = $writer;
+ if($type===null)
+ $type=$this->getHtmlWriterType();
+ return Prado::createComponent($type,$writer);
+ }
+
+ public function flush()
+ {
+ foreach($this->_writers as $writer)
+ echo $writer->flush();
+ parent::flush();
+ }
}
-
-
+class TCallbackResponseWriter extends TTextWriter
+{
+ private $_boundary;
+
+ public function __construct()
+ {
+ $this->_boundary = sprintf('%x',crc32((string)$this));
+ }
+
+ public function getBoundary()
+ {
+ return $this->_boundary;
+ }
+
+ public function setBoundary($value)
+ {
+ $this->_boundary = $value;
+ }
+
+ public function flush()
+ {
+ $content = '<!--'.$this->getBoundary().'-->';
+ $content .= parent::flush();
+ $content .= '<!--//'.$this->getBoundary().'-->';
+ return $content;
+ }
+}
?>
diff --git a/framework/Web/UI/TControl.php b/framework/Web/UI/TControl.php
index 1db01df6..860b6baf 100644
--- a/framework/Web/UI/TControl.php
+++ b/framework/Web/UI/TControl.php
@@ -1233,12 +1233,11 @@ class TControl extends TApplicationComponent implements IRenderable, IBindable
$control->evaluateDynamicContent();
}
}
+
+ if($this->getEnabled() && $this instanceof IPostBackDataHandler)
+ $this->getPage()->registerPostDataLoader($this);
}
$this->_stage=self::CS_PRERENDERED;
-
-
- if($this->getEnabled() && $this instanceof IPostBackDataHandler)
- $this->getPage()->registerPostDataLoader($this);
}
/**
diff --git a/framework/Web/UI/THtmlWriter.php b/framework/Web/UI/THtmlWriter.php
index 8a092460..0243d365 100644
--- a/framework/Web/UI/THtmlWriter.php
+++ b/framework/Web/UI/THtmlWriter.php
@@ -110,6 +110,15 @@ class THtmlWriter extends TApplicationComponent implements ITextWriter
$this->_writer=$writer;
}
+ public function getWriter()
+ {
+ return $this->_writer;
+ }
+
+ public function setWriter($writer)
+ {
+ $this->_writer = $writer;
+ }
/**
* Adds a list of attributes to be rendered.
* @param array list of attributes to be rendered
diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php
index c72cdd03..598fb9dc 100644
--- a/framework/Web/UI/TPage.php
+++ b/framework/Web/UI/TPage.php
@@ -862,7 +862,7 @@ class TPage extends TTemplateControl
*/
public function ensureRenderInForm($control)
{
- if(!$this->_inFormRender)
+ if(!$this->getIsCallback() && !$this->_inFormRender)
throw new TConfigurationException('page_control_outofform',get_class($control),$control->getUniqueID());
}
diff --git a/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page b/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page
index c20e810b..87522f72 100644
--- a/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page
+++ b/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page
@@ -1,22 +1,31 @@
<com:TContent ID="Content">
+ <h1>TCallback Demo</h1>
<com:TClientScript UsingPradoScripts="ajax" />
- <com:TActiveControl id="control1"
- ClientSide.CausesValidation="false"
- ClientSide.OnSuccess="Logger.info('result:'+result)" />
+ <com:TCallback id="control1"
+ OnCallback="control1onCallback">
+ <com:TButton id="button2" Text="Hello" Visible="false"/>
+ </com:TCallback>
<com:TTextBox id="text1" />
- <com:TRequiredFieldValidator
- ControlToValidate="text1"
- ErrorMessage="*" />
+
<com:TCheckBoxList>
<com:TListItem Text="One" />
<com:TListItem Text="Two" />
</com:TCheckBoxList>
+
+ <com:TPanel id="panel2" Visible="false">
+ asdad <com:TCheckBox id="checkbox1" />
+ </com:TPanel>
+
+ <input type="hidden" name="PRADO_CALLBACK_TARGET" value="<%= $this->control1->UniqueID %>" />
+
<com:TButton id="button1" Text="Submit" CausesValidation="false" />
<script>
Event.observe("<%= $this->button1->ClientID %>", "click", function(event)
{
<%= $this->control1->CallbackReference %>
+
Event.stop(event);
});
+
</script>
</com:TContent> \ No newline at end of file
diff --git a/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.php b/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.php
new file mode 100644
index 00000000..43d93d30
--- /dev/null
+++ b/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.php
@@ -0,0 +1,15 @@
+<?php
+/*
+ * Created on 2/05/2006
+ */
+
+class ActiveControl extends TPage
+{
+ public function control1onCallback($sender, $param)
+ {
+ $this->button2->setVisible(true);
+ $this->button2->setText("Time is ".time());
+ $this->control1->render($param->getOutput());
+ }
+}
+?>