summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwei <>2006-06-17 01:55:05 +0000
committerwei <>2006-06-17 01:55:05 +0000
commitb3ceed048bb533a00bbea542f7c12b49c8c83d9b (patch)
tree97962fc3cb6746ae404c4f1d0095834bbb0e1ac7
parent6c0154fb4e292ad22667e618f598a37cc5f9d524 (diff)
Update changes to active controls, add FT tests for active controls, add comments.
-rw-r--r--.gitignore1
-rw-r--r--framework/Exceptions/messages.txt2
-rw-r--r--framework/Web/Javascripts/js/ajax.js33
-rw-r--r--framework/Web/Javascripts/js/prado.js9
-rw-r--r--framework/Web/Javascripts/prado/activecontrols3.js16
-rw-r--r--framework/Web/Javascripts/prado/ajax3.js57
-rw-r--r--framework/Web/Javascripts/prado/element.js15
-rw-r--r--framework/Web/UI/ActiveControls/TActiveButton.php24
-rw-r--r--framework/Web/UI/ActiveControls/TActiveControlAdapter.php95
-rw-r--r--framework/Web/UI/ActiveControls/TActiveLabel.php2
-rw-r--r--framework/Web/UI/ActiveControls/TActivePageAdapter.php109
-rw-r--r--framework/Web/UI/ActiveControls/TActivePanel.php50
-rw-r--r--framework/Web/UI/ActiveControls/TActiveTextBox.php69
-rw-r--r--framework/Web/UI/ActiveControls/TAutoComplete.php2
-rw-r--r--framework/Web/UI/ActiveControls/TBaseActiveControl.php49
-rw-r--r--framework/Web/UI/ActiveControls/TCallback.php2
-rw-r--r--framework/Web/UI/ActiveControls/TCallbackClientScript.php23
-rw-r--r--framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php14
-rw-r--r--framework/Web/UI/ActiveControls/TCallbackOptions.php26
-rwxr-xr-xframework/Web/UI/ActiveControls/TCallbackResponseAdapter.php78
-rw-r--r--framework/Web/UI/TClientScriptManager.php16
-rw-r--r--framework/Web/UI/TPage.php2
-rw-r--r--framework/Web/UI/WebControls/TBaseValidator.php11
-rw-r--r--framework/Web/UI/WebControls/TClientScript.php39
-rw-r--r--framework/interfaces.php10
-rwxr-xr-xframework/prado-cli.php413
-rw-r--r--index.html1
-rw-r--r--tests/FunctionalTests/active-controls/index.php20
-rw-r--r--tests/FunctionalTests/active-controls/protected/.htaccess1
-rwxr-xr-xtests/FunctionalTests/active-controls/protected/application.xml11
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page9
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.php16
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.page22
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.php12
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page5
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.php11
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page48
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php34
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/Calculator.page31
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/Calculator.php12
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/CallbackOptionsTest.page34
-rwxr-xr-xtests/FunctionalTests/active-controls/protected/pages/FeatureList.page3
-rwxr-xr-xtests/FunctionalTests/active-controls/protected/pages/FeatureList.php34
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/Home.page1
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.page28
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.php19
-rw-r--r--tests/FunctionalTests/active-controls/tests/ActiveButtonTestCase.php16
-rw-r--r--tests/FunctionalTests/active-controls/tests/ActivePanelTestCase.php16
-rw-r--r--tests/FunctionalTests/active-controls/tests/CalculatorTestCase.php24
-rw-r--r--tests/FunctionalTests/active-controls/tests/CallbackOptionsTestCase.php34
-rw-r--r--tests/FunctionalTests/active-controls/tests/NestedActiveControlsTestCase.php26
-rw-r--r--tests/FunctionalTests/active-controls/tests/TextBoxCallbackTestCase.php17
-rwxr-xr-xtests/FunctionalTests/active.php8
-rw-r--r--tests/FunctionalTests/index.php1
54 files changed, 1514 insertions, 147 deletions
diff --git a/.gitignore b/.gitignore
index 9e781339..354a0eba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/.project
+/assets
buildscripts/jsbuilder/JSDoc
demos/quickstart/assets/274d44dc
demos/quickstart/assets/56624407
diff --git a/framework/Exceptions/messages.txt b/framework/Exceptions/messages.txt
index 257a4914..5fa94202 100644
--- a/framework/Exceptions/messages.txt
+++ b/framework/Exceptions/messages.txt
@@ -307,6 +307,8 @@ callback_not_support_no_priority_state_update = Callback request does not suppor
callback_invalid_callback_options = '{1}' is not a valid TCallbackOptions control for Callback control '{0}'.
callback_invalid_clientside_options = Callback ClientSide property must be either a string that is the ID of a TCallbackOptions control or an instance of TCallbackClientSideOptions.=======
callback_not_support_no_priority_state_update = Callback request does not support unprioritized pagestate update.
+callback_invalid_handler = Invalid callback handler, control {0} must implement ICallbackEventHandler.
+callback_invalid_target = Invalid callback target, no such control with ID {0}.
xmltransform_xslextension_required = TXmlTransform require the PHP's XSL extension
xmltransform_transformpath_invalid = TransformPath '{0}' is invalid.
diff --git a/framework/Web/Javascripts/js/ajax.js b/framework/Web/Javascripts/js/ajax.js
index 6e6d0afb..bfc080e9 100644
--- a/framework/Web/Javascripts/js/ajax.js
+++ b/framework/Web/Javascripts/js/ajax.js
@@ -33,20 +33,16 @@ this.transport.onreadystatechange=Prototype.emptyFunction;},getHeaderData:functi
{var json=this.header(name);return eval('('+json+')');}
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',FIELD_CALLBACK_PAGESTATE:'PRADO_PAGESTATE',FIELD_POSTBACK_TARGET:'PRADO_POSTBACK_TARGET',FIELD_POSTBACK_PARAMETER:'PRADO_POSTBACK_PARAMETER',PostDataLoaders:[],DATA_HEADER:'X-PRADO-DATA',ACTION_HEADER:'X-PRADO-ACTIONS',ERROR_HEADER:'X-PRADO-ERROR',PAGESTATE_HEADER:'X-PRADO-PAGESTATE',requestInProgress:null,dispatchActions:function(transport,actions)
+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',FIELD_POSTBACK_TARGET:'PRADO_POSTBACK_TARGET',FIELD_POSTBACK_PARAMETER:'PRADO_POSTBACK_PARAMETER',PostDataLoaders:[],DATA_HEADER:'X-PRADO-DATA',ACTION_HEADER:'X-PRADO-ACTIONS',ERROR_HEADER:'X-PRADO-ERROR',PAGESTATE_HEADER:'X-PRADO-PAGESTATE',requestInProgress:null,addPostLoaders:function(ids)
+{this.PostDataLoaders=this.PostDataLoaders.concat(ids);},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)
{try
{method.toFunction().apply(this,command[method].concat(transport));}
catch(e)
{if(typeof(Logger)!="undefined")
-Prado.CallbackRequest.Exception.onException(null,e);}}
-else if(typeof(Logger)!="undefined")
-{Logger.error("Error in executing callback response:","Unable to find HTML element with ID '"+id+"' before executing "+method+"().");}}}},Exception:{"on500":function(request,transport,data)
+Prado.CallbackRequest.Exception.onException(null,e);}}},Exception:{"on500":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)
{var msg='HTTP '+transport.status+" with response : \n";msg+=transport.responseText+"\n";msg+="Data : \n"+inspect(data)+"\n";msg+="Actions : \n";data=request.getHeaderData(Prado.CallbackRequest.ACTION_HEADER);if(data&&data.length>0)
@@ -63,19 +59,17 @@ msg+=e.version+" "+e.time+"\n";return msg;}},encode:function(data)
{if(typeof(data)=="string"&&data.trim().length>0)
return Prado.JSON.parse(data);else
return null;},dispatchPriorityRequest:function(callback)
-{Logger.info("priority request "+callback.id)
-this.abortRequestInProgress();callback.request=new Ajax.Request(callback.url,callback.options);callback.timeout=setTimeout(function()
-{Logger.warn("priority timeout");Prado.CallbackRequest.abortRequestInProgress();},callback.options.RequestTimeOut);this.requestInProgress=callback;Logger.info("dispatched "+this.requestInProgress)},dispatchNormalRequest:function(callback)
-{Logger.info("dispatching normal request");new Ajax.Request(callback.url,callback.options);},abortRequestInProgress:function()
-{inProgress=Prado.CallbackRequest.requestInProgress;Logger.info("aborting ... "+inProgress);if(inProgress)
-{Logger.warn("aborted "+inProgress.id)
-inProgress.request.transport.abort();clearTimeout(inProgress.timeout);Prado.CallbackRequest.requestInProgress=null;return true;}
+{this.abortRequestInProgress();callback.request=new Ajax.Request(callback.url,callback.options);callback.timeout=setTimeout(function()
+{Prado.CallbackRequest.abortRequestInProgress();},callback.options.RequestTimeOut);this.requestInProgress=callback;},dispatchNormalRequest:function(callback)
+{new Ajax.Request(callback.url,callback.options);},abortRequestInProgress:function()
+{inProgress=Prado.CallbackRequest.requestInProgress;if(inProgress)
+{inProgress.request.transport.abort();clearTimeout(inProgress.timeout);Prado.CallbackRequest.requestInProgress=null;return true;}
return false;},updatePageState:function(request,transport)
{pagestate=$(this.FIELD_CALLBACK_PAGESTATE);if(request.options.EnablePageStateUpdate&&request.options.HasPriority&&pagestate)
{data=request.header(this.PAGESTATE_HEADER);if(typeof(data)=="string"&&data.length>0)
-{Logger.warn("updating page state");pagestate.value=data;}
-else
-{Logger.debug("Bad page state:"+data);}}}})
+pagestate.value=data;else
+{if(typeof(Logger)!="undefined")
+Logger.debug("Bad page state:"+data);}}}})
Ajax.Responders.register({onComplete:function(request)
{if(request.options.HasPriority)
Prado.CallbackRequest.abortRequestInProgress();}});Event.OnLoad(function()
@@ -199,4 +193,7 @@ this.editField=this.cached_selectTag;if(this.options.loadTextURL)this.loadExtern
{this.options=options;this.baseInitialize(options.ID,options.ResultPanel,options);Object.extend(this.options,{onSuccess:this.onComplete.bind(this)});},getUpdatedChoices:function()
{Prado.Callback(this.options.EventTarget,this.getToken(),null,this.options);},onComplete:function(request,boundary)
{result=Prado.Element.extractContent(request.responseText,boundary);if(typeof(result)=="string"&&result.length>0)
-this.updateChoices(result);}}); \ No newline at end of file
+this.updateChoices(result);}});Prado.WebUI.TActiveTextBox=Class.extend(Prado.WebUI.TTextBox,{onInit:function(options)
+{if(options['TextMode']!='MultiLine')
+Event.observe(this.element,"keydown",this.handleReturnKey.bind(this));Event.observe(this.element,"change",this.doCallback.bindEvent(this,options));},doCallback:function(event,options)
+{new Prado.CallbackRequest(options.EventTarget,options);Event.stop(event);}}); \ No newline at end of file
diff --git a/framework/Web/Javascripts/js/prado.js b/framework/Web/Javascripts/js/prado.js
index 65ffd1b8..d0744649 100644
--- a/framework/Web/Javascripts/js/prado.js
+++ b/framework/Web/Javascripts/js/prado.js
@@ -267,10 +267,15 @@ setTimeout(function(){obj.focus();},100);return false;},replace:function(element
{if(boundary)
{result=Prado.Element.extractContent(transport.responseText,boundary);if(result!=null)
content=result;}
-method.toFunction().apply(this,[element,content]);},extractContent:function(text,boundary)
+if(typeof(element)=="string")
+{if($(element))
+method.toFunction().apply(this,[element,content]);}
+else
+{method.toFunction().apply(this,[content]);}},extractContent:function(text,boundary)
{f=RegExp('(<!--'+boundary+'-->)([\\s\\S\\w\\W]*)(<!--//'+boundary+'-->)',"m");result=text.match(f);if(result&&result.length>=2)
return result[2];else
-return null;}}
+return null;},evaluateScript:function(content)
+{content.evalScripts();}}
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/activecontrols3.js b/framework/Web/Javascripts/prado/activecontrols3.js
index 4be2779a..6fbde405 100644
--- a/framework/Web/Javascripts/prado/activecontrols3.js
+++ b/framework/Web/Javascripts/prado/activecontrols3.js
@@ -42,3 +42,19 @@ Prado.WebUI.TAutoComplete = Class.extend(Autocompleter.Base,
this.updateChoices(result);
}
});
+
+Prado.WebUI.TActiveTextBox = Class.extend(Prado.WebUI.TTextBox,
+{
+ onInit : function(options)
+ {
+ if(options['TextMode'] != 'MultiLine')
+ Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
+ Event.observe(this.element, "change", this.doCallback.bindEvent(this,options));
+ },
+
+ doCallback : function(event, options)
+ {
+ new Prado.CallbackRequest(options.EventTarget, options);
+ Event.stop(event);
+ }
+});
diff --git a/framework/Web/Javascripts/prado/ajax3.js b/framework/Web/Javascripts/prado/ajax3.js
index 1e832889..adf8534a 100644
--- a/framework/Web/Javascripts/prado/ajax3.js
+++ b/framework/Web/Javascripts/prado/ajax3.js
@@ -19,7 +19,7 @@ Object.extend(Ajax.Request.prototype,
Prado.CallbackRequest.updatePageState(this,transport);
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);
@@ -57,10 +57,7 @@ Object.extend(Ajax.Request.prototype,
catch (e)
{
if(typeof(json) == "string")
- {
- Logger.info("using json")
return Prado.CallbackRequest.decode(json);
- }
}
}
});
@@ -118,6 +115,14 @@ Object.extend(Prado.CallbackRequest,
requestInProgress : null,
/**
+ * Add ids of inputs element to post in the request.
+ */
+ addPostLoaders : function(ids)
+ {
+ this.PostDataLoaders = this.PostDataLoaders.concat(ids);
+ },
+
+ /**
* Dispatch callback response actions.
*/
dispatchActions : function(transport,actions)
@@ -133,26 +138,14 @@ Object.extend(Prado.CallbackRequest,
{
for(var method in command)
{
- if(command[method][0])
+ try
{
- var id = command[method][0];
- if($(id) || id.indexOf("[]") > -1)
- {
- try
- {
- method.toFunction().apply(this,command[method].concat(transport));
- }
- catch(e)
- {
- if(typeof(Logger) != "undefined")
- Prado.CallbackRequest.Exception.onException(null,e);
- }
- }
- else if(typeof(Logger) != "undefined")
- {
- Logger.error("Error in executing callback response:",
- "Unable to find HTML element with ID '"+id+"' before executing "+method+"().");
- }
+ method.toFunction().apply(this,command[method].concat(transport));
+ }
+ catch(e)
+ {
+ if(typeof(Logger) != "undefined")
+ Prado.CallbackRequest.Exception.onException(null,e);
}
}
},
@@ -251,18 +244,18 @@ Object.extend(Prado.CallbackRequest,
*/
dispatchPriorityRequest : function(callback)
{
- Logger.info("priority request "+callback.id)
+ //Logger.info("priority request "+callback.id)
this.abortRequestInProgress();
callback.request = new Ajax.Request(callback.url, callback.options);
callback.timeout = setTimeout(function()
{
- Logger.warn("priority timeout");
+ // Logger.warn("priority timeout");
Prado.CallbackRequest.abortRequestInProgress();
},callback.options.RequestTimeOut);
this.requestInProgress = callback;
- Logger.info("dispatched "+this.requestInProgress)
+ //Logger.info("dispatched "+this.requestInProgress)
},
/**
@@ -270,7 +263,7 @@ Object.extend(Prado.CallbackRequest,
*/
dispatchNormalRequest : function(callback)
{
- Logger.info("dispatching normal request");
+ // Logger.info("dispatching normal request");
new Ajax.Request(callback.url, callback.options);
},
@@ -280,10 +273,10 @@ Object.extend(Prado.CallbackRequest,
abortRequestInProgress : function()
{
inProgress = Prado.CallbackRequest.requestInProgress;
- Logger.info("aborting ... "+inProgress);
+ //Logger.info("aborting ... "+inProgress);
if(inProgress)
{
- Logger.warn("aborted "+inProgress.id)
+ // Logger.warn("aborted "+inProgress.id)
inProgress.request.transport.abort();
clearTimeout(inProgress.timeout);
Prado.CallbackRequest.requestInProgress = null;
@@ -303,13 +296,11 @@ Object.extend(Prado.CallbackRequest,
{
data = request.header(this.PAGESTATE_HEADER);
if(typeof(data) == "string" && data.length > 0)
- {
- Logger.warn("updating page state");
pagestate.value = data;
- }
else
{
- Logger.debug("Bad page state:"+data);
+ if(typeof(Logger) != "undefined")
+ Logger.debug("Bad page state:"+data);
}
}
}
diff --git a/framework/Web/Javascripts/prado/element.js b/framework/Web/Javascripts/prado/element.js
index 0ff0e8fa..5c76d4e6 100644
--- a/framework/Web/Javascripts/prado/element.js
+++ b/framework/Web/Javascripts/prado/element.js
@@ -71,7 +71,15 @@ Prado.Element =
if(result != null)
content = result;
}
- method.toFunction().apply(this,[element,content]);
+ if(typeof(element) == "string")
+ {
+ if($(element))
+ method.toFunction().apply(this,[element,content]);
+ }
+ else
+ {
+ method.toFunction().apply(this,[content]);
+ }
},
extractContent : function(text, boundary)
@@ -82,6 +90,11 @@ Prado.Element =
return result[2];
else
return null;
+ },
+
+ evaluateScript : function(content)
+ {
+ content.evalScripts();
}
}
diff --git a/framework/Web/UI/ActiveControls/TActiveButton.php b/framework/Web/UI/ActiveControls/TActiveButton.php
index f4bfc678..81744057 100644
--- a/framework/Web/UI/ActiveControls/TActiveButton.php
+++ b/framework/Web/UI/ActiveControls/TActiveButton.php
@@ -7,9 +7,11 @@
* @copyright Copyright &copy; 2006 PradoSoft
* @license http://www.pradosoft.com/license/
* @version $Revision: $ $Date: $
- * @package System.Web.UI.WebControls
+ * @package System.Web.UI.ActiveControls
*/
+Prado::using('System.Web.UI.ActiveControls.TActiveControlAdapter');
+
/**
* TActiveButton is the active control counter part to TButton.
*
@@ -17,7 +19,7 @@
* callback request is initiated.
*
* The {@link onCallback OnCallback} event is raised during a callback request
- * and it is raise before the {@link onClick OnClick} event.
+ * and it is raise <b>after</b> the {@link onClick OnClick} event.
*
* When the {@link TBaseActiveCallbackControl::setEnableUpdate ActiveControl.EnableUpdate}
* property is true, changing the {@link setText Text} property during callback request
@@ -28,7 +30,7 @@
* @package System.Web.UI.ActiveControls
* @since 3.0
*/
-class TActiveButton extends TButton implements ICallbackEventHandler
+class TActiveButton extends TButton implements ICallbackEventHandler, IActiveControl
{
/**
* Creates a new callback control, sets the adapter to
@@ -53,15 +55,15 @@ class TActiveButton extends TButton implements ICallbackEventHandler
* 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 first and then the {@link onClick OnClick} event.
+ * validate validate} method first. It will raise {@link onClick
+ * OnClick} event first and then the {@link onCallback OnCallback} event.
* This method is mainly used by framework and control developers.
* @param TCallbackEventParameter the event parameter
*/
public function raiseCallbackEvent($param)
{
- $this->onCallback($param);
$this->raisePostBackEvent($param);
+ $this->onCallback($param);
}
/**
@@ -115,16 +117,6 @@ class TActiveButton extends TButton implements ICallbackEventHandler
{
return 'Prado.WebUI.TActiveButton';
}
-
- /**
- * Gets the name of the javascript class responsible for performing postback for this control.
- * This method overrides the parent implementation.
- * @return string the javascript class name
- */
- protected function getClientClassName()
- {
- return 'Prado.WebUI.TActiveButton';
- }
}
?> \ No newline at end of file
diff --git a/framework/Web/UI/ActiveControls/TActiveControlAdapter.php b/framework/Web/UI/ActiveControls/TActiveControlAdapter.php
index ab842976..de6310a6 100644
--- a/framework/Web/UI/ActiveControls/TActiveControlAdapter.php
+++ b/framework/Web/UI/ActiveControls/TActiveControlAdapter.php
@@ -1,26 +1,68 @@
<?php
+/**
+ * TActiveControlAdapter and TCallbackPageStateTracker class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ : $
+ * @package System.Web.UI.ActiveControls
+ */
+
/*
- * Created on 29/04/2006
+ * Load common active control options.
*/
Prado::using('System.Web.UI.ActiveControls.TBaseActiveControl');
+/**
+ * TActiveControlAdapter class.
+ *
+ * Customize the parent TControl class for active control classes.
+ * TActiveControlAdapter instantiates a common base active control class
+ * throught the {@link getBaseActiveControl BaseActiveControl} property.
+ * The type of BaseActiveControl can be provided in the second parameter in the
+ * constructor. Default is TBaseActiveControl or TBaseActiveCallbackControl if
+ * the control adapted implements ICallbackEventHandler.
+ *
+ * TActiveControlAdapter will tracking viewstate changes to update the
+ * corresponding client-side properties.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 20:35:34 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
class TActiveControlAdapter extends TControlAdapter
{
- private static $_renderedPosts = false;
-
+ /**
+ * @var string base active control class name.
+ */
private $_activeControlType;
-
+ /**
+ * @var TBaseActiveControl base active control instance.
+ */
private $_baseActiveControl;
-
+ /**
+ * @var TCallbackPageStateTracker view state tracker.
+ */
private $_stateTracker;
- public function __construct($control, $baseCallbackClass=null)
+ /**
+ * Constructor.
+ * @param IActiveControl active control to adapt.
+ * @param string Base active control class name.
+ */
+ public function __construct(IActiveControl $control, $baseCallbackClass=null)
{
parent::__construct($control);
- $this->setBaseControlType($baseCallbackClass);
+ $this->setBaseControlClass($baseCallbackClass);
}
- private function setBaseControlType($type)
+ /**
+ * @param string base active control instance
+ */
+ protected function setBaseControlClass($type)
{
if(is_null($type))
{
@@ -30,22 +72,21 @@ class TActiveControlAdapter extends TControlAdapter
$this->_activeControlType = 'TBaseActiveControl';
}
else
- {
$this->_activeControlType = $type;
- }
}
/**
- * Render the callback request post data loaders once only.
+ * Renders the callback client scripts.
*/
public function render($writer)
{
$this->renderCallbackClientScripts();
parent::render($writer);
- if($this->getPage()->getIsCallback())
- $this->getPage()->getCallbackClient()->replace($this->getControl(), $writer);
}
+ /**
+ * Register the callback clientscripts and sets the post loader IDs.
+ */
protected function renderCallbackClientScripts()
{
$cs = $this->getPage()->getClientScript();
@@ -54,11 +95,14 @@ class TActiveControlAdapter extends TControlAdapter
{
$cs->registerPradoScript('ajax');
$options = TJavascript::encode($this->getPage()->getPostDataLoaders(),false);
- $script = "Prado.CallbackRequest.PostDataLoaders = {$options};";
+ $script = "Prado.CallbackRequest.addPostLoaders({$options});";
$cs->registerEndScript($key, $script);
}
}
+ /**
+ * @return TBaseActiveControl Common active control options.
+ */
public function getBaseActiveControl()
{
if(is_null($this->_baseActiveControl))
@@ -69,6 +113,9 @@ class TActiveControlAdapter extends TControlAdapter
return $this->_baseActiveControl;
}
+ /**
+ * @return boolean true if the viewstate needs to be tracked.
+ */
protected function getIsTrackingPageState()
{
if($this->getPage()->getIsCallback())
@@ -83,6 +130,10 @@ class TActiveControlAdapter extends TControlAdapter
return false;
}
+ /**
+ * Loads additional persistent control state. Starts viewstate tracking
+ * if necessary.
+ */
public function loadState()
{
if($this->getIsTrackingPageState())
@@ -93,6 +144,10 @@ class TActiveControlAdapter extends TControlAdapter
parent::loadState();
}
+ /**
+ * Saves additional persistent control state. Respond to viewstate changes
+ * if necessary.
+ */
public function saveState()
{
if(!is_null($this->_stateTracker)
@@ -104,6 +159,18 @@ class TActiveControlAdapter extends TControlAdapter
}
}
+/**
+ * TCallbackPageStateTracker class.
+ *
+ * Tracking changes to the page state during callback.
+ *
+ * @todo Complete this class! (Wei)
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 20:51:25 EST 2006 $
+ * @package System
+ * @since 3.0
+ */
class TCallbackPageStateTracker
{
private $_states = array('Visible', 'Enabled', 'Attributes', 'Style', 'TabIndex', 'ToolTip', 'AccessKey');
diff --git a/framework/Web/UI/ActiveControls/TActiveLabel.php b/framework/Web/UI/ActiveControls/TActiveLabel.php
index c1cb1fba..f54e0b98 100644
--- a/framework/Web/UI/ActiveControls/TActiveLabel.php
+++ b/framework/Web/UI/ActiveControls/TActiveLabel.php
@@ -25,7 +25,7 @@
* @package System.Web.UI.ActiveControls
* @since 3.0
*/
-class TActiveLabel extends TLabel
+class TActiveLabel extends TLabel implements IActiveControl
{
/**
* Creates a new callback control, sets the adapter to
diff --git a/framework/Web/UI/ActiveControls/TActivePageAdapter.php b/framework/Web/UI/ActiveControls/TActivePageAdapter.php
index 77d8a7fe..90c64820 100644
--- a/framework/Web/UI/ActiveControls/TActivePageAdapter.php
+++ b/framework/Web/UI/ActiveControls/TActivePageAdapter.php
@@ -1,6 +1,6 @@
<?php
/**
- * TActivePageAdapter class file.
+ * TActivePageAdapter, TCallbackEventParameter, TCallbackErrorHandler and TInvalidCallbackException class file.
*
* @author Wei Zhuo <weizhuo[at]gamil[dot]com>
* @link http://www.pradosoft.com/
@@ -9,11 +9,16 @@
* @version $Revision: $ $Date: $
* @package System.Web.UI.ActiveControls
*/
-
+
+/**
+ * Load callback response adapter class.
+ */
+Prado::using('System.Web.UI.ActiveControls.TCallbackResponseAdapter');
+
/**
* TActivePageAdapter class.
*
- * Callback request page handler.
+ * Callback request handler.
*
* @author Wei Zhuo <weizhuo[at]gamil[dot]com>
* @version $Revision: $ $Date: $
@@ -22,9 +27,21 @@
*/
class TActivePageAdapter extends TControlAdapter
{
+ /**
+ * Callback response data header name.
+ */
const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
+ /**
+ * Callback response client-side action header name.
+ */
const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
+ /**
+ * Callback error header name.
+ */
const CALLBACK_ERROR_HEADER = 'X-PRADO-ERROR';
+ /**
+ * Callback page state header name.
+ */
const CALLBACK_PAGESTATE_HEADER = 'X-PRADO-PAGESTATE';
/**
@@ -57,6 +74,7 @@ class TActivePageAdapter extends TControlAdapter
/**
* Process the callback request.
+ * @param THtmlWriter html content writer.
*/
public function processCallbackEvent($writer)
{
@@ -64,6 +82,9 @@ class TActivePageAdapter extends TControlAdapter
$this->raiseCallbackEvent();
}
+ /**
+ * Trap errors and exceptions to be handled by TCallbackErrorHandler.
+ */
protected function trapCallbackErrorsExceptions()
{
$this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
@@ -71,6 +92,7 @@ class TActivePageAdapter extends TControlAdapter
/**
* Render the callback response.
+ * @param THtmlWriter html content writer.
*/
public function renderCallbackResponse($writer)
{
@@ -81,14 +103,12 @@ class TActivePageAdapter extends TControlAdapter
/**
* Renders the callback response by adding additional callback data and
* javascript actions in the header and page state if required.
+ * @param THtmlWriter html content writer.
*/
protected function renderResponse($writer)
{
$response = $this->getResponse();
- $executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute()->toArray();
- $actions = TJavascript::jsonEncode($executeJavascript);
- $response->appendHeader(self::CALLBACK_ACTION_HEADER.': '.$actions);
-
+
//send response data in header
if($response->getHasAdapter())
{
@@ -109,12 +129,29 @@ class TActivePageAdapter extends TControlAdapter
$response->appendHeader(self::CALLBACK_PAGESTATE_HEADER.': '.$pagestate);
}
}
+
+ //safari must receive at least 1 byte of data.
+ $writer->write(" ");
+
+ //output the end javascript
+ if($this->getPage()->getClientScript()->hasEndScripts())
+ {
+ $writer = $response->createHtmlWriter();
+ $this->getPage()->getClientScript()->renderEndScripts($writer);
+ $this->getPage()->getCallbackClient()->evaluateScript($writer);
+ }
+
+ //output the actions
+ $executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute()->toArray();
+ $actions = TJavascript::jsonEncode($executeJavascript);
+ $response->appendHeader(self::CALLBACK_ACTION_HEADER.': '.$actions);
+
}
/**
* Trys to find the callback event handler and raise its callback event.
- * @throws TInvalidCallbackRequestException if call back target is not found.
- * @throws TInvalidCallbackHandlerException if the requested target does not
+ * @throws TInvalidCallbackException if call back target is not found.
+ * @throws TInvalidCallbackException if the requested target does not
* implement ICallbackEventHandler.
*/
private function raiseCallbackEvent()
@@ -129,13 +166,14 @@ class TActivePageAdapter extends TControlAdapter
}
else
{
- throw new TInvalidCallbackHandlerException($callbackHandler->getUniqueID());
+ throw new TInvalidCallbackException(
+ 'callback_invalid_handler', $callbackHandler->getUniqueID());
}
}
else
{
$target = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
- throw new TInvalidCallbackRequestException($target);
+ throw new TInvalidCallbackException('callback_invalid_target', $target);
}
}
@@ -164,7 +202,7 @@ class TActivePageAdapter extends TControlAdapter
}
/**
- * Callback parameter is decoded assuming JSON encoding.
+ * Gets callback parameter. JSON encoding is assumed.
* @return string postback event parameter
*/
public function getCallbackEventParameter()
@@ -187,7 +225,7 @@ class TActivePageAdapter extends TControlAdapter
}
/**
- * Gets the callback client script handler that allows javascript functions
+ * Gets the callback client script handler. It handlers the javascript functions
* to be executed during the callback response.
* @return TCallbackClientScript callback client handler.
*/
@@ -267,16 +305,34 @@ class TCallbackEventParameter extends TEventParameter
}
}
+/**
+ * TCallbackErrorHandler class.
+ *
+ * Captures errors and exceptions and send them back during callback response.
+ * When the application is in debug mode, the error and exception stack trace
+ * are shown. A TJavascriptLogger must be present on the client-side to view
+ * the error stack trace.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 19:11:47 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
class TCallbackErrorHandler extends TErrorHandler
{
+ /**
+ * Displays the exceptions to the client-side TJavascriptLogger.
+ * A HTTP 500 status code is sent and the stack trace is sent as JSON encoded.
+ * @param Exception exception details.
+ */
protected function displayException($exception)
{
if($this->getApplication()->getMode()===TApplication::STATE_DEBUG)
{
$response = $this->getApplication()->getResponse();
- $data = TJavascript::jsonEncode($this->getExceptionData($exception));
+ $trace = TJavascript::jsonEncode($this->getExceptionStackTrace($exception));
$response->appendHeader('HTTP/1.0 500 Internal Error');
- $response->appendHeader(TActivePageAdapter::CALLBACK_ERROR_HEADER.': '.$data);
+ $response->appendHeader(TActivePageAdapter::CALLBACK_ERROR_HEADER.': '.$trace);
}
else
{
@@ -286,9 +342,13 @@ class TCallbackErrorHandler extends TErrorHandler
$this->getApplication()->getResponse()->flush();
}
- private function getExceptionData($exception)
+ /**
+ * @param Exception exception details.
+ * @return array exception stack trace details.
+ */
+ private function getExceptionStackTrace($exception)
{
- $data['code']=$exception->getCode() > 0 ? $exception->getCode() : 505;
+ $data['code']=$exception->getCode() > 0 ? $exception->getCode() : 500;
$data['file']=$exception->getFile();
$data['line']=$exception->getLine();
$data['trace']=$exception->getTrace();
@@ -310,13 +370,16 @@ class TCallbackErrorHandler extends TErrorHandler
}
}
-class TInvalidCallbackHandlerException extends TException
+/**
+ * TInvalidCallbackException class.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 19:17:13 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
+class TInvalidCallbackException extends TException
{
-
}
-class TInvalidCallbackRequestException extends TException
-{
-}
-
?> \ No newline at end of file
diff --git a/framework/Web/UI/ActiveControls/TActivePanel.php b/framework/Web/UI/ActiveControls/TActivePanel.php
index db1d8a7d..787b65d3 100644
--- a/framework/Web/UI/ActiveControls/TActivePanel.php
+++ b/framework/Web/UI/ActiveControls/TActivePanel.php
@@ -1,9 +1,37 @@
<?php
-/*
- * Created on 5/05/2006
+/**
+ * TActivePanel file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version : :
+ * @package System.Web.UI.ActiveControls
*/
-class TActivePanel extends TPanel
+/**
+ * TActivePanel is the TPanel active control counterpart.
+ *
+ * TActivePanel allows the client-side panel contents to be updated during a
+ * callback response using the {@link flush} method.
+ *
+ * Example: Assume $param is an instance of TCallbackEventParameter attached to
+ * the OnCallback event a TCallback with ID "callback1", and
+ * "panel1" is the ID of a TActivePanel.
+ * <code>
+ * function callback1_requested($sender, $param)
+ * {
+ * $this->panel1->flush($param->getOutput());
+ * }
+ * </code>
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 01:23:54 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
+class TActivePanel extends TPanel implements IActiveControl
{
/**
* Creates a new callback control, sets the adapter to
@@ -16,10 +44,26 @@ class TActivePanel extends TPanel
$this->setAdapter(new TActiveControlAdapter($this));
}
+ /**
+ * @return TBaseActiveControl standard active control options.
+ */
public function getActiveControl()
{
return $this->getAdapter()->getBaseActiveControl();
}
+
+ /**
+ * Renders and replaces the panel's content on the client-side.
+ * @param THtmlWriter html writer
+ */
+ public function flush($writer)
+ {
+ if($this->getActiveControl()->canUpdateClientSide())
+ {
+ $this->render($writer);
+ $this->getPage()->getCallbackClient()->replaceContent($this,$writer);
+ }
+ }
}
?> \ No newline at end of file
diff --git a/framework/Web/UI/ActiveControls/TActiveTextBox.php b/framework/Web/UI/ActiveControls/TActiveTextBox.php
index ece08e11..2e207f92 100644
--- a/framework/Web/UI/ActiveControls/TActiveTextBox.php
+++ b/framework/Web/UI/ActiveControls/TActiveTextBox.php
@@ -1,9 +1,30 @@
<?php
-/*
- * Created on 6/05/2006
+/**
+ * TActiveTextBox class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ : $
+ * @package System.Web.UI.ActiveControls
*/
-class TActiveTextBox extends TTextBox
+/**
+ * TActiveTextBox class.
+ *
+ * TActiveTextBox allows the {@link setText Text} property of the textbox to
+ * be changed during callback. When {@link setAutoPostBack AutoPostBack} property
+ * is true, changes to the textbox contents will perform a callback request causing
+ * {@link onTextChanged OnTextChange} to be fired first followed by {@link onCallback OnCallback}
+ * event.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 20:05:16 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
+class TActiveTextBox extends TTextBox implements ICallbackEventHandler, IActiveControl
{
/**
* Creates a new callback control, sets the adapter to
@@ -15,7 +36,10 @@ class TActiveTextBox extends TTextBox
parent::__construct();
$this->setAdapter(new TActiveControlAdapter($this));
}
-
+
+ /**
+ * @return TBaseActiveCallbackControl standard callback control options.
+ */
public function getActiveControl()
{
return $this->getAdapter()->getBaseActiveControl();
@@ -31,6 +55,43 @@ class TActiveTextBox extends TTextBox
if($this->getActiveControl()->canUpdateClientSide() && $this->getHasLoadedPostData())
$this->getPage()->getCallbackClient()->setValue($this, $value);
}
+
+ /**
+ * 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 onTextChanged
+ * onTextChanged} event first and then the {@link onCallback OnCallback} event.
+ * This method is mainly used by framework and control developers.
+ * @param TCallbackEventParameter the event parameter
+ */
+ public function raiseCallbackEvent($param)
+ {
+ $this->raisePostDataChangedEvent($param);
+ $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);
+ }
+
+ /**
+ * Renders the javascript for textbox.
+ */
+ protected function renderClientControlScript($writer)
+ {
+ $writer->addAttribute('id',$this->getClientID());
+ $this->getActiveControl()->registerCallbackClientScript(
+ $this->getClientClassName(), $this->getPostBackOptions());
+ }
/**
* Gets the name of the javascript class responsible for performing postback for this control.
diff --git a/framework/Web/UI/ActiveControls/TAutoComplete.php b/framework/Web/UI/ActiveControls/TAutoComplete.php
index 63b1d089..601894ff 100644
--- a/framework/Web/UI/ActiveControls/TAutoComplete.php
+++ b/framework/Web/UI/ActiveControls/TAutoComplete.php
@@ -3,7 +3,7 @@
* Created on 7/05/2006
*/
-class TAutoComplete extends TActiveTextBox implements ICallbackEventHandler, INamingContainer
+class TAutoComplete extends TActiveTextBox implements INamingContainer
{
/**
* @var ITemplate template for repeater items
diff --git a/framework/Web/UI/ActiveControls/TBaseActiveControl.php b/framework/Web/UI/ActiveControls/TBaseActiveControl.php
index 13d953c8..71d50a67 100644
--- a/framework/Web/UI/ActiveControls/TBaseActiveControl.php
+++ b/framework/Web/UI/ActiveControls/TBaseActiveControl.php
@@ -10,6 +10,8 @@
* @package System.Web.UI.ActiveControls
*/
+Prado::using('System.Web.UI.ActiveControls.TCallbackClientSideOptions');
+
/**
* TBaseActiveControl class provided additional basic property for every
* active control. An instance of TBaseActiveControl or its decendent
@@ -163,42 +165,66 @@ class TBaseActiveCallbackControl extends TBaseActiveControl
}
return $client;
}
+
+ /**
+ * Sets the client side options. Can only be set when client side is null.
+ * @param TCallbackClientSideOptions client side options.
+ */
+ public function setClientSide($client)
+ {
+ if(is_null($this->getOption('ClientSide')))
+ $this->setOption('ClientSide', $client);
+ else
+ throw new TConfigurationException(
+ 'active_controls_client_side_exists', $this->getControl()->getID());
+ }
/**
* @return TCallbackClientSideOptions callback client-side options.
*/
protected function createClientSideOptions()
{
- if(($id=$this->getCallbackOptionID())!==''
- && ($control=$this->getControl()->findControl($id))!==null)
- {
- if($control instanceof TCallbackOptions)
- return $control->getClientSide();
- }
return new TCallbackClientSideOptions;
}
/**
- * Sets the ID of a TCallbackOptions component to duplicate the client-side
+ * Sets default callback options. Takes the ID of a TCallbackOptions
+ * component to duplicate the client-side
* options for this control. The {@link getClientSide ClientSide}
* subproperties has precendent over the CallbackOptions property.
* @param string ID of a TCallbackOptions control from which ClientSide
* options are cloned.
*/
- public function setCallbackOptionID($value)
+ public function setCallbackOptions($value)
{
$this->setOption('CallbackOptions', $value, '');
}
/**
* @return string ID of a TCallbackOptions control from which ClientSide
- * options are cloned.
+ * options are duplicated.
*/
- public function getCallbackOptionID()
+ public function getCallbackOptions()
{
return $this->getOption('CallbackOptions', '');
}
+ /**
+ * Returns an array of default callback client-side options. The default options
+ * are obtained from the client-side options of a TCallbackOptions control with
+ * ID specified by {@link setCallbackOptionsID CallbackOptionsID}.
+ * @return array list of default callback client-side options.
+ */
+ protected function getDefaultClientSideOptions()
+ {
+ if(($id=$this->getCallbackOptions())!==''
+ && ($control=$this->getControl()->findControl($id))!==null
+ && $control instanceof TCallbackOptions)
+ return $control->getClientSide()->getOptions()->toArray();
+ else
+ return array();
+ }
+
/**
* @return boolean whether callback event trigger by this button will cause
* input validation, default is true
@@ -255,7 +281,8 @@ class TBaseActiveCallbackControl extends TBaseActiveControl
*/
protected function getClientSideOptions()
{
- $options = $this->getClientSide()->getOptions()->toArray();
+ $default = $this->getDefaultClientSideOptions();
+ $options = array_merge($default,$this->getClientSide()->getOptions()->toArray());
$validate = $this->getCausesValidation();
$options['CausesValidation']= $validate ? '' : false;
$options['ValidationGroup']=$this->getValidationGroup();
diff --git a/framework/Web/UI/ActiveControls/TCallback.php b/framework/Web/UI/ActiveControls/TCallback.php
index c42b2cd7..3c7d70c5 100644
--- a/framework/Web/UI/ActiveControls/TCallback.php
+++ b/framework/Web/UI/ActiveControls/TCallback.php
@@ -35,7 +35,7 @@
* @package System.Web.UI.ActiveControls
* @since 3.0
*/
-class TCallback extends TControl implements ICallbackEventHandler
+class TCallback extends TControl implements ICallbackEventHandler, IActiveControl
{
/**
* Creates a new callback control, sets the adapter to
diff --git a/framework/Web/UI/ActiveControls/TCallbackClientScript.php b/framework/Web/UI/ActiveControls/TCallbackClientScript.php
index 5d317c8a..d83bf90a 100644
--- a/framework/Web/UI/ActiveControls/TCallbackClientScript.php
+++ b/framework/Web/UI/ActiveControls/TCallbackClientScript.php
@@ -313,7 +313,7 @@ class TCallbackClientScript extends TApplicationComponent
* @see insertBefore
* @see insertAfter
*/
- public function replace($element, $content, $method="Element.replace", $boundary=null)
+ protected function replace($element, $content, $method="Element.replace", $boundary=null)
{
if($content instanceof TControl)
{
@@ -325,12 +325,31 @@ class TCallbackClientScript extends TApplicationComponent
$boundary = $this->getResponseContentBoundary($content);
$content = null;
}
-
+
$this->callClientFunction('Prado.Element.replace',
array($element, $method, $content, $boundary));
}
/**
+ * Replace the content of an element with new content contained in writer.
+ * @param TControl|string control element or HTML element id.
+ * @param THtmlWriter writer for the content.
+ */
+ public function replaceContent($element,$writer)
+ {
+ $this->replace($element, $writer);
+ }
+
+ /**
+ * Evaluate a block of javascript enclosed in a boundary.
+ * @param THtmlWriter writer for the content.
+ */
+ public function evaluateScript($writer)
+ {
+ $this->replace(null, $writer, 'Prado.Element.evaluateScript');
+ }
+
+ /**
* Renders the control and return the content boundary from
* TCallbackResponseWriter. This method should only be used by framework
* component developers.
diff --git a/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php b/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php
index 3f54e013..e8c2dc26 100644
--- a/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php
+++ b/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php
@@ -266,11 +266,17 @@ class TCallbackClientSideOptions extends TClientSideOptions
return is_null($option) ? true : $option;
}
+ /**
+ * @return string post back target ID
+ */
public function getPostBackTarget()
{
return $this->getOption('EventTarget');
}
+ /**
+ * @param string post back target ID
+ */
public function setPostBackTarget($value)
{
if($value instanceof TControl)
@@ -278,17 +284,21 @@ class TCallbackClientSideOptions extends TClientSideOptions
$this->setOption('EventTarget', $value);
}
+ /**
+ * @return string post back event parameter.
+ */
public function getPostBackParameter()
{
return $this->getOption('EventParameter');
}
+ /**
+ * @param string post back event parameter.
+ */
public function setPostBackParameter($value)
{
$this->setOption('EventParameter', $value);
}
-
-
}
?> \ No newline at end of file
diff --git a/framework/Web/UI/ActiveControls/TCallbackOptions.php b/framework/Web/UI/ActiveControls/TCallbackOptions.php
index fc0becb5..588cbf8a 100644
--- a/framework/Web/UI/ActiveControls/TCallbackOptions.php
+++ b/framework/Web/UI/ActiveControls/TCallbackOptions.php
@@ -1,10 +1,31 @@
<?php
-/*
- * Created on 12/05/2006
+/**
+ * TCallbackOptions component class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ : $
+ * @package System.Web.UI.ActiveControls
*/
+/**
+ * TCallbackOptions class.
+ *
+ * TCallbackOptions allows common set of callback client-side options
+ * to be attached to other active controls.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 08:10:50 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
class TCallbackOptions extends TControl
{
+ /**
+ * @var TCallbackClientSideOptions client side callback options.
+ */
private $_clientSide;
/**
@@ -28,7 +49,6 @@ class TCallbackOptions extends TControl
{
return new TCallbackClientSideOptions;
}
-
}
?> \ No newline at end of file
diff --git a/framework/Web/UI/ActiveControls/TCallbackResponseAdapter.php b/framework/Web/UI/ActiveControls/TCallbackResponseAdapter.php
index d1ec67ad..1f11f973 100755
--- a/framework/Web/UI/ActiveControls/TCallbackResponseAdapter.php
+++ b/framework/Web/UI/ActiveControls/TCallbackResponseAdapter.php
@@ -1,14 +1,48 @@
<?php
+/**
+ * TCallbackResponseAdapter and TCallbackResponseWriter class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version 3.0
+ * @package System.Web.UI.ActiveControls
+ */
/**
+ * TCallbackResponseAdapter alters the THttpResponse's outputs.
*
+ * A TCallbackResponseWriter is used instead of the TTextWrite when
+ * createHtmlWriter is called. Each call to createHtmlWriter will create
+ * a new TCallbackResponseWriter. When flushContent() is called each
+ * instance of TCallbackResponseWriter's content is flushed.
+ *
+ * The callback response data can be set using the {@link setResponseData ResponseData}
+ * property.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 07:52:14 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
*/
class TCallbackResponseAdapter extends THttpResponseAdapter
{
+ /**
+ * @TCallbackResponseWriter[] list of writers.
+ */
private $_writers=array();
-
+ /**
+ * @mixed callback response data.
+ */
private $_data;
+ /**
+ * Returns a new instance of THtmlWriter.
+ * An instance of TCallbackResponseWriter is created to hold the content.
+ * @param string writer class name.
+ * @param THttpResponse http response handler.
+ */
public function createNewHtmlWriter($type,$response)
{
$writer = new TCallbackResponseWriter();
@@ -16,6 +50,9 @@ class TCallbackResponseAdapter extends THttpResponseAdapter
return parent::createNewHtmlWriter($type,$writer);
}
+ /**
+ * Flushes the contents in the writers.
+ */
public function flushContent()
{
foreach($this->_writers as $writer)
@@ -23,36 +60,75 @@ class TCallbackResponseAdapter extends THttpResponseAdapter
parent::flushContent();
}
+ /**
+ * @param mixed callback response data.
+ */
public function setResponseData($data)
{
$this->_data = $data;
}
+ /**
+ * @return mixed callback response data.
+ */
public function getResponseData()
{
return $this->_data;
}
}
+/**
+ * TCallbackResponseWriter class.
+ *
+ * TCallbackResponseWriter class enclosed a chunck of content within a
+ * html comment boundary. This allows multiple chuncks of content to return
+ * in the callback response and update multiple HTML elements.
+ *
+ * The {@link setBoundary Boundary} property sets boundary identifier in the
+ * HTML comment that forms the boundary. By default, the boundary identifier
+ * is generated from the object instance ID.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Sun Jun 18 08:02:21 EST 2006 $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
class TCallbackResponseWriter extends TTextWriter
{
+ /**
+ * @var string boundary ID
+ */
private $_boundary;
+ /**
+ * Constructor. Generates boundary ID using object instance ID.
+ */
public function __construct()
{
$this->_boundary = sprintf('%x',crc32((string)$this));
}
+ /**
+ * @return string boundary identifier.
+ */
public function getBoundary()
{
return $this->_boundary;
}
+ /**
+ * @param string boundary identifier.
+ */
public function setBoundary($value)
{
$this->_boundary = $value;
}
+ /**
+ * Returns the text content wrapped within a HTML comment with boundary
+ * identifier as its comment content.
+ * @return string text content chunck.
+ */
public function flush()
{
$content = '<!--'.$this->getBoundary().'-->';
diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php
index eb5e445a..8f43fbab 100644
--- a/framework/Web/UI/TClientScriptManager.php
+++ b/framework/Web/UI/TClientScriptManager.php
@@ -429,6 +429,22 @@ class TClientScriptManager extends TApplicationComponent
{
return isset($this->_endScripts[$key]);
}
+
+ /**
+ * @return boolean true if any end scripts are registered.
+ */
+ public function hasEndScripts()
+ {
+ return count($this->_endScripts) > 0;
+ }
+
+ /**
+ * @return boolean true if any begin scripts are registered.
+ */
+ public function hasBeginScripts()
+ {
+ return count($this->_beginScripts) > 0;
+ }
/**
* @param string a unique key
diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php
index a1d64765..838d0342 100644
--- a/framework/Web/UI/TPage.php
+++ b/framework/Web/UI/TPage.php
@@ -269,6 +269,8 @@ class TPage extends TTemplateControl
*/
protected function processCallbackRequest($writer)
{
+ Prado::using('System.Web.UI.ActiveControls.TActivePageAdapter');
+
$this->setAdapter(new TActivePageAdapter($this));
Prado::trace("Page onPreInit()",'System.Web.UI.TPage');
diff --git a/framework/Web/UI/WebControls/TBaseValidator.php b/framework/Web/UI/WebControls/TBaseValidator.php
index 1194089e..a0801d4c 100644
--- a/framework/Web/UI/WebControls/TBaseValidator.php
+++ b/framework/Web/UI/WebControls/TBaseValidator.php
@@ -124,7 +124,8 @@ abstract class TBaseValidator extends TLabel implements IValidator
}
/**
- * Adds attributes to renderer.
+ * Adds attributes to renderer. Calls parent implementation and renders the
+ * client control scripts.
* @param THtmlWriter the renderer
*/
protected function addAttributesToRender($writer)
@@ -137,6 +138,7 @@ abstract class TBaseValidator extends TLabel implements IValidator
$writer->addStyleAttribute('visibility','hidden');
$writer->addAttribute('id',$this->getClientID());
parent::addAttributesToRender($writer);
+ $this->renderClientControlScript($writer);
}
/**
@@ -217,11 +219,10 @@ abstract class TBaseValidator extends TLabel implements IValidator
* Renders the javascript code to the end script.
* If you override this method, be sure to call the parent implementation
* so that the event handlers can be invoked.
- * @param TEventParameter event parameter to be passed to the event handlers
+ * @param THtmlWriter the renderer
*/
- public function onPreRender($param)
+ public function renderClientControlScript($writer)
{
- parent::onPreRender($param);
$scripts = $this->getPage()->getClientScript();
$formID=$this->getPage()->getForm()->getClientID();
$scriptKey = "TBaseValidator:$formID";
@@ -236,7 +237,7 @@ abstract class TBaseValidator extends TLabel implements IValidator
$this->registerClientScriptValidator();
$this->updateControlCssClass();
}
-
+
/**
* Update the ControlToValidate component's css class depending
* if the ControlCssClass property is set, and whether this is valid.
diff --git a/framework/Web/UI/WebControls/TClientScript.php b/framework/Web/UI/WebControls/TClientScript.php
index 23aa1425..020b44b8 100644
--- a/framework/Web/UI/WebControls/TClientScript.php
+++ b/framework/Web/UI/WebControls/TClientScript.php
@@ -22,6 +22,11 @@
* <com:TClientScript UsingPradoScripts="effects, rico" />
* </code>
*
+ * The {@link setPreRenderControlTypes PreRenderControlTypes} property can
+ * be used to specify that controls type/class names that should pre-render itself
+ * even though they may not be rendered on the page. This is useful to publish
+ * controls that require assets and is only visible after a callback response.
+ *
* @TODO May be use it to include stylesheets as well.
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
@@ -54,6 +59,23 @@ class TClientScript extends TControl
}
/**
+ * @param string comma delimited list of controls that wish to be prerendered
+ * so as to publish its assets.
+ */
+ public function setPreRenderControlTypes($value)
+ {
+ $this->setViewState('PreRenderControls', $value);
+ }
+
+ /**
+ * @return string comma delimited list of controls types that require prerendering.
+ */
+ public function getPreRenderControlTypes()
+ {
+ return $this->getViewState('PreRenderControls', '');
+ }
+
+ /**
* Calls the client script manager to add each of the requested client
* script libraries.
* @param mixed event parameter
@@ -69,6 +91,23 @@ class TClientScript extends TControl
if(strlen($script) > 0)
$cs->registerPradoScript($script);
}
+ $this->preRenderControls($param);
+ }
+
+ /**
+ * PreRender other controls to allow them to publish their assets. Useful
+ * when callback response components that require assets to be present on the page.
+ * @param mixed event paramater
+ */
+ protected function preRenderControls($param)
+ {
+ $types = preg_split('/,|\s+/', $this->getPreRenderControlTypes());
+ foreach($types as $type)
+ {
+ $control = Prado::createComponent($type);
+ $control->setPage($this->getPage());
+ $control->onPreRender($param);
+ }
}
}
diff --git a/framework/interfaces.php b/framework/interfaces.php
index d87cc20f..b28d3881 100644
--- a/framework/interfaces.php
+++ b/framework/interfaces.php
@@ -292,7 +292,10 @@ interface IBindable
*/
interface IActiveControl
{
-
+ /**
+ * @return TBaseActiveControl Active control properties.
+ */
+ public function getActiveControl();
}
/**
@@ -315,11 +318,6 @@ interface ICallbackEventHandler
* @param TCallbackEventParameter the parameter associated with the callback event
*/
public function raiseCallbackEvent($eventArgument);
-
- /**
- * @return TBaseActiveControl Active control properties.
- */
- public function getActiveControl();
}
?> \ No newline at end of file
diff --git a/framework/prado-cli.php b/framework/prado-cli.php
new file mode 100755
index 00000000..f8ce826c
--- /dev/null
+++ b/framework/prado-cli.php
@@ -0,0 +1,413 @@
+#!/usr/bin/php
+<?php
+
+/**
+ * Prado command line developer tools.
+ */
+
+if(!isset($_SERVER['argv']))
+ die('Must be run from the command line');
+
+//command line options.
+$options['create'] = array('c:', 'create=', '<directory>', 'create a new project <directory>');
+$console = new ConsoleOptions($options, $_SERVER['argv']);
+
+if($console->hasOptions())
+{
+ if($dir = $console->getOption('create'))
+ create_new_prado_project($dir);
+}
+else
+{
+ $details = $console->renderOptions();
+echo <<<EOD
+Usage: php prado-cli.php [-c] <directory>
+Options:
+$details
+Example: php prado-cli.php -c ./example1
+
+
+EOD;
+}
+
+/**
+ * Functions to create new prado project.
+ */
+
+function create_new_prado_project($dir)
+{
+ if(strlen(trim($dir)) == 0)
+ return;
+
+ $rootPath = realpath(dirname(trim($dir)));
+
+ $basePath = $rootPath.'/'.basename($dir);
+ $assetPath = $basePath.'/assets';
+ $protectedPath = $basePath.'/protected';
+ $runtimePath = $basePath.'/protected/runtime';
+ $pagesPath = $protectedPath.'/pages';
+
+ $indexFile = $basePath.'/index.php';
+ $htaccessFile = $protectedPath.'/.htaccess';
+ $defaultPageFile = $pagesPath.'/Home.page';
+
+ create_directory($basePath, 0755);
+ create_directory($assetPath,0777);
+ create_directory($protectedPath,0755);
+ create_directory($runtimePath,0777);
+ create_directory($pagesPath,0755);
+
+ file_put_contents($indexFile, render_index_file());
+ file_put_contents($htaccessFile, render_htaccess_file());
+ file_put_contents($defaultPageFile, render_default_page());
+}
+
+function create_directory($dir, $mask)
+{
+ if(!is_dir($dir))
+ mkdir($dir);
+ if(is_dir($dir))
+ chmod($dir, $mask);
+}
+
+function render_index_file()
+{
+ $framework = realpath(dirname(__FILE__)).'/prado.php';
+return '<?php
+
+$basePath=dirname(__FILE__);
+$frameworkPath="'.$framework.'";
+$assetsPath=$basePath."/assets";
+$runtimePath=$basePath."/protected/runtime";
+
+if(!is_file($frameworkPath))
+ die("Unable to find prado framework path $frameworkPath.");
+if(!is_writable($assetsPath))
+ die("Please make sure that the directory $assetsPath is writable by Web server process.");
+if(!is_writable($runtimePath))
+ die("Please make sure that the directory $runtimePath is writable by Web server process.");
+
+require_once($frameworkPath);
+
+$application=new TApplication;
+$application->run();
+
+?>';
+}
+
+function render_htaccess_file()
+{
+ return 'deny from all';
+}
+
+
+function render_default_page()
+{
+return <<<EOD
+<h1>Welcome to Prado!</h1>
+EOD;
+}
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available through the world-wide-web at the following url: |
+// | http://www.php.net/license/3_0.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Andrei Zmievski <andrei@php.net> |
+// +----------------------------------------------------------------------+
+//
+// $Id: Getopt.php,v 1.21.4.7 2003/12/05 21:57:01 andrei Exp $
+
+/**
+ * Command-line options parsing class.
+ *
+ * @author Andrei Zmievski <andrei@php.net>
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ */
+class ConsoleOptions
+{
+ private $_short_options;
+ private $_long_options;
+ private $_options;
+ private $_args;
+ private $_values;
+
+ /**
+ * @param array list of options with the following format
+ * <tt>option['name'] = array('short', 'long', 'parameter', 'description')</tt>
+ * see @link setOptions} for details on 'short' and 'long' string details.
+ */
+ public function __construct($options, $args)
+ {
+ $short = '';
+ $long = array();
+ foreach($options as $option)
+ {
+ $short .= $option[0];
+ $long[] = $option[1];
+ }
+ $this->setOptions($short,$long);
+ $this->_options = $options;
+ $this->_args = $args;
+ }
+
+ /**
+ * The second parameter is a string of allowed short options. Each of the
+ * option letters can be followed by a colon ':' to specify that the option
+ * requires an argument, or a double colon '::' to specify that the option
+ * takes an optional argument.
+ *
+ * The third argument is an optional array of allowed long options. The
+ * leading '--' should not be included in the option name. Options that
+ * require an argument should be followed by '=', and options that take an
+ * option argument should be followed by '=='.
+ *
+ * @param string $short_options specifies the list of allowed short options
+ * @param array $long_options specifies the list of allowed long options
+ */
+ protected function setOptions($short_options, $long_options=null)
+ {
+ $this->_short_options = $short_options;
+ $this->_long_options = $long_options;
+ }
+
+ /**
+ * @return string list of options and its descriptions
+ */
+ public function renderOptions()
+ {
+ $options = array();
+ $descriptions = array();
+ $max = 0;
+ foreach($this->_options as $option)
+ {
+ $short = str_replace(':','',$option[0]);
+ $long = str_replace('=','',$option[1]);
+ $desc = $option[2];
+ $details = " -{$short}, --{$long} {$desc}";
+ if(($len = strlen($details)) > $max)
+ $max = $len;
+ $options[] = $details;
+ $descriptions[] = $option[3];
+ }
+
+ $content = '';
+ for($i = 0, $k = count($options); $i < $k; $i++)
+ $content .= str_pad($options[$i],$max+3).$descriptions[$i]."\n";
+ return $content;
+ }
+
+ /**
+ * @param string argument name
+ * @return string argument value
+ */
+ public function getOption($name)
+ {
+ if(is_null($this->_values))
+ $this->_values = $this->getNamedOptions();
+ return isset($this->_values[$name]) ? $this->_values[$name] : null;
+ }
+
+ /**
+ * @return array list of all options given.
+ */
+ public function getOptions()
+ {
+ if(is_null($this->_values))
+ $this->_values = $this->getNamedOptions();
+ return $this->_values;
+ }
+
+ /**
+ * @return boolean true if one or more options are given.
+ */
+ public function hasOptions()
+ {
+ if(is_null($this->_values))
+ $this->_values = $this->getNamedOptions();
+ return count($this->_values) > 0;
+ }
+
+ /**
+ * Parse the options from args into named arguements.
+ */
+ protected function getNamedOptions()
+ {
+ $options = array();
+ $values = $this->parseOptions($this->_args);
+ foreach($values[0] as $value)
+ {
+ foreach($this->_options as $name => $option)
+ {
+ if(strpos($option[0],$value[0]) !== false
+ || strpos($option[1], $value[0]) !== false)
+ $options[$name] = $value[1];
+ }
+ }
+ return $options;
+ }
+
+ /**
+ * Gets the command-line options.
+ *
+ * The parameter to this function should be the list of command-line
+ * arguments without the leading reference to the running program.
+ *
+ * The return value is an array of two elements: the list of parsed
+ * options and the list of non-option command-line arguments. Each entry in
+ * the list of parsed options is a pair of elements - the first one
+ * specifies the option, and the second one specifies the option argument,
+ * if there was one.
+ *
+ * Long and short options can be mixed.
+ *
+ * @param array $args an array of command-line arguments
+ * @return array two-element array containing the list of parsed options and
+ * the non-option arguments
+ */
+ public function parseOptions($args)
+ {
+ if (empty($args))
+ return array(array(), array());
+
+ $short_options = $this->_short_options;
+ $long_options = $this->_long_options;
+
+ $opts = array();
+ $non_opts = array();
+
+ settype($args, 'array');
+
+ if ($long_options)
+ sort($long_options);
+
+ if (isset($args[0]{0}) && $args[0]{0} != '-')
+ array_shift($args);
+
+ reset($args);
+
+ while (list($i, $arg) = each($args))
+ {
+
+ /* The special element '--' means explicit end of
+ options. Treat the rest of the arguments as non-options
+ and end the loop. */
+ if ($arg == '--') {
+ $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
+ break;
+ }
+
+ if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options))
+ {
+ $non_opts = array_merge($non_opts, array_slice($args, $i));
+ break;
+ }
+ elseif (strlen($arg) > 1 && $arg{1} == '-')
+ $this->parseLongOption(substr($arg, 2), $long_options, $opts, $args);
+ else
+ $this->parseShortOption(substr($arg, 1), $short_options, $opts, $args);
+ }
+
+ return array($opts, $non_opts);
+ }
+
+ private function parseShortOption($arg, $short_options, &$opts, &$args)
+ {
+ for ($i = 0; $i < strlen($arg); $i++)
+ {
+ $opt = $arg{$i};
+ $opt_arg = null;
+
+ /* Try to find the short option in the specifier string. */
+ if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
+ throw new Exception("Console_Getopt: unrecognized option -- $opt");
+
+ if (strlen($spec) > 1 && $spec{1} == ':')
+ {
+ if (strlen($spec) > 2 && $spec{2} == ':')
+ {
+ if ($i + 1 < strlen($arg))
+ {
+ /* Option takes an optional argument. Use the remainder of
+ the arg string if there is anything left. */
+ $opts[] = array($opt, substr($arg, $i + 1));
+ break;
+ }
+ }
+ else
+ {
+ /* Option requires an argument. Use the remainder of the arg
+ string if there is anything left. */
+ if ($i + 1 < strlen($arg))
+ {
+ $opts[] = array($opt, substr($arg, $i + 1));
+ break;
+ }
+ else if (list(, $opt_arg) = each($args))
+ /* Else use the next argument. */;
+ else
+ throw new Exception("Console_Getopt: option requires an argument -- $opt");
+ }
+ }
+
+ $opts[] = array($opt, $opt_arg);
+ }
+ }
+
+ private function parseLongOption($arg, $long_options, &$opts, &$args)
+ {
+ @list($opt, $opt_arg) = explode('=', $arg);
+ $opt_len = strlen($opt);
+
+ for ($i = 0; $i < count($long_options); $i++)
+ {
+ $long_opt = $long_options[$i];
+ $opt_start = substr($long_opt, 0, $opt_len);
+
+ /* Option doesn't match. Go on to the next one. */
+ if ($opt_start != $opt)
+ continue;
+
+ $opt_rest = substr($long_opt, $opt_len);
+
+ /* Check that the options uniquely matches one of the allowed
+ options. */
+ if ($opt_rest != '' && $opt{0} != '=' &&
+ $i + 1 < count($long_options) &&
+ $opt == substr($long_options[$i+1], 0, $opt_len))
+ {
+ throw new Exception("Console_Getopt: option --$opt is ambiguous");
+ }
+
+ if (substr($long_opt, -1) == '=')
+ {
+ if (substr($long_opt, -2) != '==')
+ {
+ /* Long option requires an argument.
+ Take the next argument if one wasn't specified. */;
+ if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args)))
+ throw new Exception("Console_Getopt: option --$opt requires an argument");
+ }
+ }
+ else if ($opt_arg)
+ throw new Exception("Console_Getopt: option --$opt doesn't allow an argument");
+
+ $opts[] = array($opt, $opt_arg);
+ return;
+ }
+
+ throw new Exception("Console_Getopt: unrecognized option --$opt");
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/index.html b/index.html
index edd88b3e..10b05a29 100644
--- a/index.html
+++ b/index.html
@@ -97,6 +97,7 @@ The installation is done! You will see the following subdirectories,
<h3>Special Credits To</h3>
<ul>
+<li>Stever for providing an Apple G3 notebook for testing safari browser issues.</li>
<li>All PRADO users - great suggestions, feedback and support</li>
<li>ASP.NET 2.0 for its great inspiration and reference</li>
<li>All <a href="framework/3rdParty/readme.html">third-party work</a> used in PRADO</li>
diff --git a/tests/FunctionalTests/active-controls/index.php b/tests/FunctionalTests/active-controls/index.php
new file mode 100644
index 00000000..8fa57a6a
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/index.php
@@ -0,0 +1,20 @@
+<?php
+
+$basePath=dirname(__FILE__);
+$frameworkPath="/Users/weizhuo/Sites/workspace/prado-trunk/framework/prado.php";
+$assetsPath=$basePath."/assets";
+$runtimePath=$basePath."/protected/runtime";
+
+if(!is_file($frameworkPath))
+ die("Unable to find prado framework path $frameworkPath.");
+if(!is_writable($assetsPath))
+ die("Please make sure that the directory $assetsPath is writable by Web server process.");
+if(!is_writable($runtimePath))
+ die("Please make sure that the directory $runtimePath is writable by Web server process.");
+
+require_once($frameworkPath);
+
+$application=new TApplication;
+$application->run();
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/.htaccess b/tests/FunctionalTests/active-controls/protected/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/.htaccess
@@ -0,0 +1 @@
+deny from all \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/application.xml b/tests/FunctionalTests/active-controls/protected/application.xml
new file mode 100755
index 00000000..03fe9bbb
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/application.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<application id="FeatureTests" Mode="Debug">
+ <paths>
+ <using namespace="System.Web.UI.ActiveControls.*" />
+ </paths>
+ <services>
+ <service id="page" class="TPageService" DefaultPage="FeatureList">
+ </service>
+ </services>
+</application> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page b/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page
new file mode 100644
index 00000000..5c2d1abb
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page
@@ -0,0 +1,9 @@
+<com:TForm ID="form1">
+ <h1>TActiveButton Functional Test</h1>
+ <com:TActiveButton ID="button2" Text="Button 1"
+ OnClick="button2_onclick" OnCallback="button2_oncallback" />
+
+ <com:TActiveLabel ID="label1" Text="Label 1" />
+
+ <com:TJavascriptLogger />
+</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.php b/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.php
new file mode 100644
index 00000000..6282b804
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.php
@@ -0,0 +1,16 @@
+<?php
+
+class ActiveButtonTest extends TPage
+{
+ function button2_onclick($sender, $param)
+ {
+ $this->label1->Text = "Button 1 was clicked ";
+ }
+
+ function button2_oncallback($sender, $param)
+ {
+ $this->label1->Text .= "using callback!";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.page b/tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.page
new file mode 100644
index 00000000..468a524e
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.page
@@ -0,0 +1,22 @@
+<com:TForm ID="form1">
+ <h1>Active Panel replacement tests </h1>
+ <com:TCallback ID="callback1" OnCallback="callback1_requested" />
+ <com:TActivePanel ID="panel1">
+ <com:TPlaceHolder ID="content1" Visible="false">
+ Something lalala <com:TButton ID="button1" Text="Button Text"/>
+ </com:TPlaceHolder>
+ </com:TActivePanel>
+ <div id="div1" style="border:1px solid #666; background-color: #ffe; text-align: center; padding:3em">
+ Click Me!
+ </div>
+ <script type="text/javascript">
+ Event.OnLoad(function()
+ {
+ Event.observe($("div1"), "click", function()
+ {
+ Prado.Callback("<%= $this->callback1->ClientID %>")
+ })
+ })
+ </script>
+ <com:TJavascriptLogger />
+</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.php b/tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.php
new file mode 100644
index 00000000..79e3d46c
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActivePanelTest.php
@@ -0,0 +1,12 @@
+<?php
+
+class ActivePanelTest extends TPage
+{
+ function callback1_requested($sender, $param)
+ {
+ $this->content1->visible = true;
+ $this->panel1->flush($param->output);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page b/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page
new file mode 100644
index 00000000..d0a750ac
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page
@@ -0,0 +1,5 @@
+<com:TForm ID="form1">
+ <h1>ActiveTextBox Callback Test</h1>
+ <com:TActiveTextBox ID="textbox1" AutoPostBack="true" OnCallback="textbox1_callback" />
+ <com:TActiveLabel ID="label1" Text="Label 1" />
+</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.php b/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.php
new file mode 100644
index 00000000..c3f729e5
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.php
@@ -0,0 +1,11 @@
+<?php
+
+class ActiveTextBoxCallback extends TPage
+{
+ function textbox1_callback($sender, $param)
+ {
+ $this->label1->Text = 'Label 1: '.$sender->Text;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page
new file mode 100644
index 00000000..93658bd7
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page
@@ -0,0 +1,48 @@
+<com:TForm ID="form1">
+
+ <style>
+ .autocomplete
+ {
+ border:1px solid #919EA9;
+ background-color:white;
+ }
+ .autocomplete ul, .autocomplete li
+ {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ font-size: 12px;
+ font-family: Tahoma, Arial, Helvetica, sans-serif;
+ color: #333;
+ }
+
+ .autocomplete li
+ {
+ padding: 4px;
+ border-top: 1px solid #ccc;
+ }
+ .autocomplete .selected
+ {
+ background-color: #ffc;
+ }
+ </style>
+
+ <h1>TAutoComplete Test</h1>
+
+ <com:TAutoComplete Style="width: 20em"
+ OnCallback="suggestCountries"
+ Separator=", "
+ ResultPanel.CssClass="autocomplete" />
+
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <p><br /></p>
+ <com:TJavascriptLogger />
+</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php
new file mode 100644
index 00000000..938b8640
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Created on 7/05/2006
+ */
+
+class AutoCompleteTest extends TPage
+{
+ public function suggestCountries($sender, $param)
+ {
+ $sender->setDataSource($this->matchCountries($param->getParameter()));
+ $sender->dataBind();
+ $sender->render($param->getOutput());
+ }
+
+ protected function matchCountries($token)
+ {
+ $info = Prado::createComponent('System.I18N.core.CultureInfo', 'en');
+ $list = array();
+ $count = 0;
+ $token = strtolower($token);
+ foreach($info->getCountries() as $country)
+ {
+ if(strpos(strtolower($country), $token) === 0)
+ {
+ $list[] = $country;
+ $count++;
+ if($count > 10) break;
+ }
+ }
+ return $list;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/Calculator.page b/tests/FunctionalTests/active-controls/protected/pages/Calculator.page
new file mode 100644
index 00000000..7a565658
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/Calculator.page
@@ -0,0 +1,31 @@
+<com:TForm ID="form1">
+ <h1>Callback Enabled Calculator</h1>
+
+ <com:TActiveTextBox id="a" />
+ +
+ <com:TActiveTextBox id="b" />
+ =
+ <com:TActiveTextBox id="c" />
+
+ <com:TActiveButton id="sum"
+ onclick="do_sum"
+ text="Calculate!" />
+
+ <com:TRequiredFieldValidator
+ ControlToValidate="a"
+ ErrorMessage="left summand is required."
+ ControlCssClass="required"
+ Display="None" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="b"
+ ErrorMessage="right summand is requied."
+ ControlCssClass="required"
+ Display="None" />
+
+ <div class="summarybox">
+ <com:TValidationSummary
+ ID="summary"
+ HeaderText="Unable to calculate because" />
+ </div>
+
+</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/Calculator.php b/tests/FunctionalTests/active-controls/protected/pages/Calculator.php
new file mode 100644
index 00000000..098a8460
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/Calculator.php
@@ -0,0 +1,12 @@
+<?php
+
+class Calculator extends TPage
+{
+ public function do_sum($sender, $param)
+ {
+ $this->c->Text = floatval($this->a->Text) + floatval($this->b->Text);
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/CallbackOptionsTest.page b/tests/FunctionalTests/active-controls/protected/pages/CallbackOptionsTest.page
new file mode 100644
index 00000000..6e8b8a1e
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/CallbackOptionsTest.page
@@ -0,0 +1,34 @@
+<com:TForm ID="form1">
+ <h1>TCallbackOptions Test</h1>
+
+ <com:TCallbackOptions ID="options1">
+ <prop:ClientSide.OnLoading>
+ $("status").show();
+ </prop:ClientSide.OnLoading>
+ <prop:ClientSide.OnComplete>
+ $("status").hide();
+ </prop:ClientSide.OnComplete>
+ <prop:ClientSide.OnSuccess>
+ Element.update("label1", "Button 1 has returned");
+ </prop:ClientSide.OnSuccess>
+ </com:TCallbackOptions>
+
+ <com:TActiveButton id="button1" Text="Button 1" ActiveControl.CallbackOptions="options1" />
+ <com:TActiveButton id="button2" Text="Button 2" ActiveControl.CallbackOptions="options1">
+ <prop:ActiveControl.ClientSide.OnSuccess>
+ Element.update("label2", "Button 2 has returned");
+ </prop:ActiveControl.ClientSide.OnSuccess>
+ </com:TActiveButton>
+ <com:TActiveButton id="button3" Text="Button 3">
+ <prop:ActiveControl.ClientSide.OnSuccess>
+ Element.update("label3", "Button 3 has returned");
+ </prop:ActiveControl.ClientSide.OnSuccess>
+ </com:TActiveButton>
+
+ <div id="label1">Label 1</div>
+ <div id="label2">Label 2</div>
+ <div id="label3">Label 3</div>
+ <div id="status" style="display:none; background-color: #c00; color:white; text-align:center; padding: 1em" >
+ Loading...
+ </div>
+</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/FeatureList.page b/tests/FunctionalTests/active-controls/protected/pages/FeatureList.page
new file mode 100755
index 00000000..eab14a87
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/FeatureList.page
@@ -0,0 +1,3 @@
+<com:TForm ID="form1">
+ <com:TBulletedList ID="List" DisplayMode="HyperLink"/>
+</com:TForm>
diff --git a/tests/FunctionalTests/active-controls/protected/pages/FeatureList.php b/tests/FunctionalTests/active-controls/protected/pages/FeatureList.php
new file mode 100755
index 00000000..8f28f6de
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/FeatureList.php
@@ -0,0 +1,34 @@
+<?php
+
+class FeatureList extends TPage
+{
+ public function onLoad($param)
+ {
+ parent::onLoad($param);
+ $list=$this->getPageList(dirname(__FILE__),'');
+ $this->List->DataSource=$list;
+ $this->List->dataBind();
+ }
+
+ protected function getPageList($directory,$basePath)
+ {
+ $list=array();
+ $folder=@opendir($directory);
+ while($entry=@readdir($folder))
+ {
+ if($entry[0]==='.')
+ continue;
+ else if(is_file($directory.'/'.$entry))
+ {
+ if(($page=basename($entry,'.page'))!==$entry && strpos($page,'.')===false)
+ $list['?page='.$basePath.$page]=$basePath.$page;
+ }
+ else
+ $list=array_merge($list,$this->getPageList($directory.'/'.$entry,$basePath.$entry.'.'));
+ }
+ closedir($folder);
+ return $list;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/Home.page b/tests/FunctionalTests/active-controls/protected/pages/Home.page
new file mode 100644
index 00000000..ff226d4e
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/Home.page
@@ -0,0 +1 @@
+<h1>Welcome to Prado!</h1> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.page b/tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.page
new file mode 100644
index 00000000..d8eacf15
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.page
@@ -0,0 +1,28 @@
+<com:TForm ID="form1">
+ <h1>Nested Active Controls Test</h1>
+ <com:TCallback ID="callback1" OnCallback="callback1_requested" />
+ <com:TActivePanel ID="panel1">
+ <com:TPlaceHolder ID="content1" Visible="false">
+ Something lalala
+ <com:TActiveButton ID="button1" Text="Button 1" OnClick="button1_clicked" />
+ <com:TActiveLabel ID="label3" Text="Label 3" />
+
+ </com:TPlaceHolder>
+ <com:TActiveLabel ID="label1" Text="Label 1" />
+ </com:TActivePanel>
+ <div id="div1" style="border:1px solid #666; background-color: #ffe; text-align: center; padding:1em; margin: 2em">
+ Click Me!
+ </div>
+ <com:TActiveLabel ID="label2" Text="Label 2" />
+ <script type="text/javascript">
+ Event.OnLoad(function()
+ {
+ Event.observe($("div1"), "click", function()
+ {
+ Prado.Callback("<%= $this->callback1->ClientID %>")
+ })
+ })
+ </script>
+ <com:TJavascriptLogger />
+
+</com:TForm>
diff --git a/tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.php b/tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.php
new file mode 100644
index 00000000..a4a22cbf
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/protected/pages/NestedActiveControls.php
@@ -0,0 +1,19 @@
+<?php
+
+class NestedActiveControls extends TPage
+{
+ function callback1_requested($sender, $param)
+ {
+ $this->content1->visible = true;
+ $this->panel1->flush($param->output);
+ }
+
+ function button1_clicked($sender, $param)
+ {
+ $this->label1->Text = "Label 1: Button 1 Clicked";
+ $this->label2->Text = "Label 2: Button 1 Clicked";
+ $this->label3->Text = "Label 3: Button 1 Clicked";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/tests/ActiveButtonTestCase.php b/tests/FunctionalTests/active-controls/tests/ActiveButtonTestCase.php
new file mode 100644
index 00000000..0a294906
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/ActiveButtonTestCase.php
@@ -0,0 +1,16 @@
+<?php
+
+class ActiveButtonTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=ActiveButtonTest");
+ $this->verifyTextPresent("TActiveButton Functional Test");
+ $this->assertText("label1", "Label 1");
+ $this->click("button2");
+ $this->pause(500);
+ $this->assertText("label1", "Button 1 was clicked using callback!");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/tests/ActivePanelTestCase.php b/tests/FunctionalTests/active-controls/tests/ActivePanelTestCase.php
new file mode 100644
index 00000000..a9fb6c4e
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/ActivePanelTestCase.php
@@ -0,0 +1,16 @@
+<?php
+
+class ActivePanelTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=ActivePanelTest");
+ $this->verifyTextPresent("Active Panel replacement tests");
+ $this->assertTextNotPresent('Something lalala');
+ $this->click("div1");
+ $this->pause(500);
+ $this->assertTextPresent("Something lalala");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/tests/CalculatorTestCase.php b/tests/FunctionalTests/active-controls/tests/CalculatorTestCase.php
new file mode 100644
index 00000000..2fb28224
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/CalculatorTestCase.php
@@ -0,0 +1,24 @@
+<?php
+
+class CalculatorTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=Calculator");
+ $this->assertTextPresent("Callback Enabled Calculator");
+ $this->assertNotVisible("summary");
+
+ $this->click("sum");
+ $this->assertVisible("summary");
+
+ $this->type("a", "2");
+ $this->type("b", "5");
+
+ $this->click("sum");
+ $this->assertNotVisible("summary");
+
+ $this->assertValue("c", "7");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/tests/CallbackOptionsTestCase.php b/tests/FunctionalTests/active-controls/tests/CallbackOptionsTestCase.php
new file mode 100644
index 00000000..d72499cf
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/CallbackOptionsTestCase.php
@@ -0,0 +1,34 @@
+<?php
+
+class CallbackOptionsTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=CallbackOptionsTest");
+ $this->verifyTextPresent("TCallbackOptions Test");
+
+ $this->assertText("label1", "Label 1");
+ $this->assertText("label2", "Label 2");
+ $this->assertText("label3", "Label 3");
+
+ $this->click("button1");
+ $this->pause(500);
+ $this->assertText("label1", "Button 1 has returned");
+ $this->assertText("label2", "Label 2");
+ $this->assertText("label3", "Label 3");
+
+ $this->click("button2");
+ $this->pause(500);
+ $this->assertText("label1", "Button 1 has returned");
+ $this->assertText("label2", "Button 2 has returned");
+ $this->assertText("label3", "Label 3");
+
+ $this->click("button3");
+ $this->pause(500);
+ $this->assertText("label1", "Button 1 has returned");
+ $this->assertText("label2", "Button 2 has returned");
+ $this->assertText("label3", "Button 3 has returned");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/tests/NestedActiveControlsTestCase.php b/tests/FunctionalTests/active-controls/tests/NestedActiveControlsTestCase.php
new file mode 100644
index 00000000..8d0cfb87
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/NestedActiveControlsTestCase.php
@@ -0,0 +1,26 @@
+<?php
+
+class NestedActiveControlsTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=NestedActiveControls");
+ $this->verifyTextPresent("Nested Active Controls Test");
+ $this->assertText("label1", "Label 1");
+ $this->assertText("label2", "Label 2");
+ $this->assertTextNotPresent("Label 3");
+
+ $this->click("div1");
+ $this->pause(500);
+ $this->assertTextPresent("Something lalala");
+ $this->assertText("label3", "Label 3");
+
+ $this->click("button1");
+ $this->pause(500);
+ $this->assertText("label1", "Label 1: Button 1 Clicked");
+ $this->assertText("label2", "Label 2: Button 1 Clicked");
+ $this->assertText("label3", "Label 3: Button 1 Clicked");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/tests/TextBoxCallbackTestCase.php b/tests/FunctionalTests/active-controls/tests/TextBoxCallbackTestCase.php
new file mode 100644
index 00000000..24cba49b
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/TextBoxCallbackTestCase.php
@@ -0,0 +1,17 @@
+<?php
+
+class TextBoxCallbackTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=ActiveTextBoxCallback");
+ $this->verifyTextPresent("ActiveTextBox Callback Test");
+ $this->assertText("label1", "Label 1");
+
+ $this->type("textbox1", "hello!");
+ $this->pause(500);
+ $this->assertText("label1", "Label 1: hello!");
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/active.php b/tests/FunctionalTests/active.php
new file mode 100755
index 00000000..ca6ab635
--- /dev/null
+++ b/tests/FunctionalTests/active.php
@@ -0,0 +1,8 @@
+<?php
+
+require(dirname(__FILE__).'/PradoTester.php');
+
+$tester=new PradoTester(dirname(__FILE__).'/active-controls/tests');
+$tester->run(new SimpleReporter());
+
+?> \ No newline at end of file
diff --git a/tests/FunctionalTests/index.php b/tests/FunctionalTests/index.php
index c22543c0..ceba599a 100644
--- a/tests/FunctionalTests/index.php
+++ b/tests/FunctionalTests/index.php
@@ -9,6 +9,7 @@ Prado Functional Test Suites
<ul>
<li><a href="quickstart.php">Tests of QuickStart Tutorial Demo</a></li>
<li><a href="validators.php">Tests of Validators</a></li>
+ <li><a href="active.php">Tests of Active Controls</a> (<a href="active-controls/index.php">list of test samples</a>) </li>
<li><a href="tickets.php">Tests of Trac Tickets</a></li>
<li><a href="features.php">Tests of New Features</a> (<a href="features/index.php">list of new features</a>)</li>
</ul>