diff options
| author | wei <> | 2006-05-04 02:02:43 +0000 | 
|---|---|---|
| committer | wei <> | 2006-05-04 02:02:43 +0000 | 
| commit | 75a0a250cc6735d13b3b782daf0127298b37c2b9 (patch) | |
| tree | fc9a1a0b28022d573097dcc7e71b29dfe6c5821f | |
| parent | d255f4d0e332740b3984e21ce3f7a4a4f1968ba3 (diff) | |
Adding TCallback component.
| -rw-r--r-- | .gitattributes | 2 | ||||
| -rw-r--r-- | framework/Web/Javascripts/js/ajax.js | 18 | ||||
| -rw-r--r-- | framework/Web/Javascripts/js/prado.js | 10 | ||||
| -rw-r--r-- | framework/Web/Javascripts/prado/ajax3.js | 49 | ||||
| -rw-r--r-- | framework/Web/Javascripts/prado/element.js | 12 | ||||
| -rw-r--r-- | framework/Web/UI/ActiveControls/TActiveControl.php | 125 | ||||
| -rw-r--r-- | framework/Web/UI/ActiveControls/TActivePageAdapter.php | 33 | ||||
| -rw-r--r-- | framework/Web/UI/ActiveControls/TCallback.php | 165 | ||||
| -rw-r--r-- | framework/Web/UI/ActiveControls/TCallbackClientScript.php | 109 | ||||
| -rw-r--r-- | framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php | 68 | ||||
| -rw-r--r-- | framework/Web/UI/ActiveControls/TCallbackResponse.php | 45 | ||||
| -rw-r--r-- | framework/Web/UI/TControl.php | 7 | ||||
| -rw-r--r-- | framework/Web/UI/THtmlWriter.php | 9 | ||||
| -rw-r--r-- | framework/Web/UI/TPage.php | 2 | ||||
| -rw-r--r-- | tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page | 21 | ||||
| -rw-r--r-- | tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.php | 15 | 
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)
 +	{
 +		$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($innerHTML instanceof TControl)
 -			$innerHTML = $innerHTML->render();
 -		$this->callClientFunction('Prado.Element.Insert.Above', array($element, $innerHTML));
 -	}	
 +		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());
 +	}
 +}
 +?>
  | 
