diff options
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | framework/Web/Javascripts/TJavaScript.php | 15 | ||||
-rw-r--r-- | framework/Web/Javascripts/js/ajax.js | 53 | ||||
-rw-r--r-- | framework/Web/Javascripts/js/prado.js | 4 | ||||
-rw-r--r-- | framework/Web/Javascripts/js/validator.js | 3 | ||||
-rw-r--r-- | framework/Web/Javascripts/prado/ajax3.js | 213 | ||||
-rw-r--r-- | framework/Web/Javascripts/prado/validation3.js | 9 | ||||
-rw-r--r-- | framework/Web/UI/ActiveControls/TActiveControl.php | 28 | ||||
-rw-r--r-- | framework/Web/UI/ActiveControls/TActiveControlAdapter.php | 2 | ||||
-rw-r--r-- | framework/Web/UI/ActiveControls/TActivePageAdapter.php | 164 | ||||
-rw-r--r-- | framework/Web/UI/ActiveControls/TCallbackClientScript.php | 4 | ||||
-rw-r--r-- | framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php | 209 | ||||
-rw-r--r-- | framework/Web/UI/ActiveControls/TCallbackResponse.php | 3 | ||||
-rw-r--r-- | framework/Web/UI/TClientScriptManager.php | 15 | ||||
-rw-r--r-- | framework/Web/UI/TControl.php | 7 | ||||
-rw-r--r-- | framework/Web/UI/TPage.php | 19 | ||||
-rw-r--r-- | tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page | 13 |
17 files changed, 635 insertions, 127 deletions
diff --git a/.gitattributes b/.gitattributes index a9170941..58bdd50b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -931,6 +931,7 @@ 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/TCallbackClientScript.php -text +framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php -text framework/Web/UI/ActiveControls/TCallbackResponse.php -text framework/Web/UI/TClientScriptManager.php -text framework/Web/UI/TControl.php -text diff --git a/framework/Web/Javascripts/TJavaScript.php b/framework/Web/Javascripts/TJavaScript.php index 75fc2438..0f6414ec 100644 --- a/framework/Web/Javascripts/TJavaScript.php +++ b/framework/Web/Javascripts/TJavaScript.php @@ -24,6 +24,11 @@ class TJavaScript
{
/**
+ * @var TJSON JSON decoder and encoder instance
+ */
+ private static $_json;
+
+ /**
* Renders a list of javascript files
* @param array URLs to the javascript files
* @return string rendering result
@@ -191,8 +196,9 @@ class TJavaScript */
public static function jsonEncode($value)
{
- Prado::using('System.Web.Javascripts.TJSON');
- return TJSON::encode($value);
+ if(is_null(self::$_json))
+ self::$_json = Prado::createComponent('System.Web.Javascripts.TJSON');
+ return self::$_json->encode($value);
}
/**
@@ -203,8 +209,9 @@ class TJavaScript */
public static function jsonDecode($value)
{
- Prado::using('System.Web.Javascripts.TJSON');
- return TJSON::decode($value);
+ if(is_null(self::$_json))
+ self::$_json = Prado::createComponent('System.Web.Javascripts.TJSON');
+ return self::$_json->decode($value);
}
}
diff --git a/framework/Web/Javascripts/js/ajax.js b/framework/Web/Javascripts/js/ajax.js index 2b509d61..6ae737bf 100644 --- a/framework/Web/Javascripts/js/ajax.js +++ b/framework/Web/Javascripts/js/ajax.js @@ -20,28 +20,53 @@ this.transport=Ajax.getTransport();this.setOptions(options);var onComplete=this. response=response.stripScripts();if(receiver){if(this.options.insertion){new this.options.insertion(receiver,response);}else{Element.update(receiver,response);}} 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);}});Prado.Callback=Class.create();Object.extend(Prado.Callback,{FIELD_CALLBACK_TARGET:'PRADO_CALLBACK_TARGET',FIELD_CALLBACK_PARAMETER:'PRADO_CALLBACK_PARAMETER',PostDataLoaders:['PRADO_PAGESTATE'],Exception:{"on505":function(request,transport,data) -{var msg='HTTP '+transport.status+" with response";Logger.error(msg,transport.responseText);this.logException(data);},onComplete:function(request,transport,data) -{if(transport.status!=505) -{var msg='HTTP '+transport.status+" with response : \n";msg+=transport.responseText+"\n";msg+="Data : \n"+inspect(data);Logger.warn(msg);}},formatException:function(e) +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);} +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);} +if(event=='Complete') +this.transport.onreadystatechange=Prototype.emptyFunction;},getHeaderData:function(name) +{try +{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',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) +{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") +{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) +{var msg='HTTP '+transport.status+" with response : \n";msg+=transport.responseText+"\n";msg+="Data : \n"+inspect(data)+"\n";msg+="Actions : \n";request.getHeaderData(Prado.CallbackRequest.ACTION_HEADER).each(function(action) +{msg+=inspect(action)+"\n";}) +Logger.warn(msg);}},onException:function(e) +{Logger.error('Uncaught Callback Client Exception:',e);},formatException:function(e) {var msg=e.type+" with message \""+e.message+"\"";msg+=" in "+e.file+"("+e.line+")\n";msg+="Stack trace:\n";var trace=e.trace;for(var i=0;i<trace.length;i++) {msg+=" #"+i+" "+trace[i].file;msg+="("+trace[i].line+"): ";msg+=trace[i]["class"]+"->"+trace[i]["function"]+"()"+"\n";} -return msg;},logException:function(e) -{Logger.error("Callback Request Error "+e.code,this.formatException(e));}},encode:function(data) -{Prado.JSON.stringify(data);},decode:function(data) +msg+=e.version+" "+e.time+"\n";return msg;}},encode:function(data) +{return Prado.JSON.stringify(data);},decode:function(data) {return Prado.JSON.parse(data);}}) Event.OnLoad(function() {if(typeof Logger!="undefined") -Ajax.Responders.register(Prado.Callback.Exception);});Prado.Callback.prototype={url:window.location.href,options:{},id:null,parameters:null,initialize:function(id,parameters,onSuccess,options) -{this.options=options||{};this.id=id;this.parameters=parameters;var request={postBody:this._getPostData(),onSuccess:this._onSuccess.bind(this)} -Object.extend(this.options||{},request);new Ajax.Request(this.url,this.options);},_getPostData:function() -{var data={};Prado.Callback.PostDataLoaders.each(function(name) +Ajax.Responders.register(Prado.CallbackRequest.Exception);});Prado.CallbackRequest.prototype={url:window.location.href,options:{},id:null,request:null,initialize:function(id,options) +{this.options=options||{};this.id=id;var request={postBody:this._getPostData(),parameters:''} +Object.extend(this.options||{},request);if(this.options.CausesValidation!=false&&typeof(Prado.Validation)!="undefined") +{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) {$A(document.getElementsByName(name)).each(function(element) {var value=$F(element);if(typeof(value)!="undefined") data[name]=value;})}) -if(typeof(this.parameters)!="undefined") -data[Prado.Callback.FIELD_CALLBACK_PARAMETER]=Prado.Callback.encode(this.parameters);data[Prado.Callback.FIELD_CALLBACK_TARGET]=this.id;return $H(data).toQueryString();},_onSuccess:function(response,transport,json) -{}} +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();}} +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+=',';} s+=v;} return'['+s+']';}else if(typeof arg.toString!='undefined'){for(i in arg){v=arg[i];if(typeof v!='undefined'&&typeof v!='function'){v=this.stringify(v);if(s){s+=',';} diff --git a/framework/Web/Javascripts/js/prado.js b/framework/Web/Javascripts/js/prado.js index 498a6cf7..d80f984d 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(typeof(el.onclick)=="function") +{el.fireEvent('onclick');if(isFunction(el.onclick)) el.onclick();}},setAttribute:function(element,attribute,value) {var el=$(element);if(attribute=="disabled"&&value==false) el.removeAttribute(attribute);else @@ -265,7 +265,7 @@ 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(typeof(obj)!="undefined"&&typeof(obj.focus)!="undefined") +{var obj=$(element);if(isObject(obj)&&isdef(obj.focus)) setTimeout(function(){obj.focus();},100);return false;}} Prado.Element.Selection={inputValue:function(el,value) {switch(el.type.toLowerCase()) diff --git a/framework/Web/Javascripts/js/validator.js b/framework/Web/Javascripts/js/validator.js index 66b48866..41b40fcd 100644 --- a/framework/Web/Javascripts/js/validator.js +++ b/framework/Web/Javascripts/js/validator.js @@ -3,7 +3,8 @@ Prado.Validation=Class.create();Object.extend(Prado.Validation,{managers:{},vali {if(this.managers[formID]) {return this.managers[formID].validate(groupID,invoker);} else -{throw new Error("Form '"+form+"' is not registered with Prado.Validation");}},isValid:function(formID,groupID) +{throw new Error("Form '"+form+"' is not registered with Prado.Validation");}},getForm:function() +{var keys=$H(this.managers).keys();return keys[0];},isValid:function(formID,groupID) {if(this.managers[formID]) return this.managers[formID].isValid(groupID);return true;},addValidator:function(formID,validator) {if(this.managers[formID]) diff --git a/framework/Web/Javascripts/prado/ajax3.js b/framework/Web/Javascripts/prado/ajax3.js index ded63104..1cffbd73 100644 --- a/framework/Web/Javascripts/prado/ajax3.js +++ b/framework/Web/Javascripts/prado/ajax3.js @@ -1,27 +1,131 @@ /**
+ * Override Prototype's response implementation.
+ */
+Object.extend(Ajax.Request.prototype,
+{
+ /**
+ * Customize the response, dispatch onXXX response code events, and
+ * tries to execute response actions (javascript statements).
+ */
+ 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);
+ }
+ 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);
+ }
+
+ /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+ if (event == 'Complete')
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ },
+
+ /**
+ * Gets header data assuming JSON encoding.
+ * @param string header name
+ * @return object header data as javascript structures.
+ */
+ getHeaderData : function(name)
+ {
+ try
+ {
+ var json = this.header(name);
+ return eval('(' + json + ')');
+ }
+ catch (e)
+ {
+ if(typeof(json) == "string")
+ {
+ Logger.info("using json")
+ return Prado.CallbackRequest.decode(json);
+ }
+ }
+ }
+});
+
+/**
* Prado Callback client-side request handler.
*/
-Prado.Callback = Class.create();
+Prado.CallbackRequest = Class.create();
/**
* Static definitions.
*/
-Object.extend(Prado.Callback,
+Object.extend(Prado.CallbackRequest,
{
/**
* Callback request target POST field name.
*/
FIELD_CALLBACK_TARGET : 'PRADO_CALLBACK_TARGET',
-
/**
* Callback request parameter POST field name.
*/
FIELD_CALLBACK_PARAMETER : 'PRADO_CALLBACK_PARAMETER',
-
/**
* List of form fields that will be collected during callback.
*/
PostDataLoaders : ['PRADO_PAGESTATE'],
+ /**
+ * Response data header name.
+ */
+ DATA_HEADER : 'X-PRADO-DATA',
+ /**
+ * Response javascript execution statement header name.
+ */
+ ACTION_HEADER : 'X-PRADO-ACTIONS',
+ /**
+ * Response errors/exceptions header name.
+ */
+ ERROR_HEADER : 'X-PRADO-ERROR',
+
+ /**
+ * Dispatch callback response actions.
+ */
+ dispatchActions : function(actions)
+ {
+ actions.each(this.__run);
+ },
+
+ /**
+ * Prase and evaluate a Callback clien-side action
+ */
+ __run : function(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")
+ {
+ Logger.error("Error in executing callback response:",
+ "Unable to find HTML element with ID '"+id+"' before executing "+method+"().");
+ }
+ }
+ }
+ },
/**
* Respond to Prado Callback request exceptions.
@@ -32,27 +136,40 @@ Object.extend(Prado.Callback, * Server returns 505 exception. Just log it.
*/
"on505" : function(request, transport, data)
- {
- var msg = 'HTTP '+transport.status+" with response";
- Logger.error(msg, transport.responseText);
- this.logException(data);
+ {
+ var e = request.getHeaderData(Prado.CallbackRequest.ERROR_HEADER);
+ Logger.error("Callback Server Error "+e.code, this.formatException(e));
},
/**
* Callback OnComplete event,logs reponse and data to console.
*/
- onComplete : function(request, transport, data)
+ 'on200' : function(request, transport, data)
{
- if(transport.status != 505)
+ if(transport.status < 500)
{
var msg = 'HTTP '+transport.status+" with response : \n";
msg += transport.responseText + "\n";
- msg += "Data : \n"+inspect(data);
+ msg += "Data : \n"+inspect(data)+"\n";
+ msg += "Actions : \n";
+ request.getHeaderData(Prado.CallbackRequest.ACTION_HEADER).each(function(action)
+ {
+ msg += inspect(action)+"\n";
+ })
+
Logger.warn(msg);
}
},
/**
+ * Uncaught exceptions during callback response.
+ */
+ onException : function(e)
+ {
+ Logger.error('Uncaught Callback Client Exception:', e);
+ },
+
+ /**
* Formats the exception message for display in console.
*/
formatException : function(e)
@@ -67,15 +184,8 @@ Object.extend(Prado.Callback, msg += "("+trace[i].line+"): ";
msg += trace[i]["class"]+"->"+trace[i]["function"]+"()"+"\n";
}
+ msg += e.version+" "+e.time+"\n";
return msg;
- },
-
- /**
- * Log Callback response exceptions to console.
- */
- logException : function(e)
- {
- Logger.error("Callback Request Error "+e.code, this.formatException(e));
}
},
@@ -84,7 +194,7 @@ Object.extend(Prado.Callback, */
encode : function(data)
{
- Prado.JSON.stringify(data);
+ return Prado.JSON.stringify(data);
},
/**
@@ -100,13 +210,13 @@ Object.extend(Prado.Callback, Event.OnLoad(function()
{
if(typeof Logger != "undefined")
- Ajax.Responders.register(Prado.Callback.Exception);
+ Ajax.Responders.register(Prado.CallbackRequest.Exception);
});
/**
* Create and prepare a new callback request.
*/
-Prado.Callback.prototype =
+Prado.CallbackRequest.prototype =
{
/**
* Callback URL, same url as the current page.
@@ -124,27 +234,31 @@ Prado.Callback.prototype = id : null,
/**
- * Callback parameters.
+ * Current callback request.
*/
- parameters : null,
+ request : null,
/**
* Prepare and inititate a callback request.
*/
- initialize : function(id, parameters, onSuccess, options)
+ initialize : function(id, options)
{
this.options = options || {};
this.id = id;
- this.parameters = parameters;
var request =
{
postBody : this._getPostData(),
- onSuccess : this._onSuccess.bind(this)
+ parameters : ''
}
Object.extend(this.options || {},request);
-
- new Ajax.Request(this.url, this.options);
+ if(this.options.CausesValidation != false && typeof(Prado.Validation) != "undefined")
+ {
+ 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);
},
/**
@@ -156,7 +270,7 @@ Prado.Callback.prototype = {
var data = {};
- Prado.Callback.PostDataLoaders.each(function(name)
+ Prado.CallbackRequest.PostDataLoaders.each(function(name)
{
$A(document.getElementsByName(name)).each(function(element)
{
@@ -165,17 +279,32 @@ Prado.Callback.prototype = data[name] = value;
})
})
- if(typeof(this.parameters) != "undefined")
- data[Prado.Callback.FIELD_CALLBACK_PARAMETER] = Prado.Callback.encode(this.parameters);
- data[Prado.Callback.FIELD_CALLBACK_TARGET] = this.id;
+ 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();
- },
-
- /**
- * Dispatch a successfull response to the appropriate responders.
- */
- _onSuccess : function(response, transport, json)
- {
- //Logger.info("asd");
}
-}
\ No newline at end of file +}
+
+/**
+ * Create a new callback request using default settings.
+ * @param string callback handler unique ID.
+ * @param mixed parameter to pass to callback handler on the server side.
+ * @param function client side onSuccess event handler.
+ * @param object additional request options.
+ * @return boolean always false.
+ */
+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;
+}
diff --git a/framework/Web/Javascripts/prado/validation3.js b/framework/Web/Javascripts/prado/validation3.js index 1f154fec..8becac70 100644 --- a/framework/Web/Javascripts/prado/validation3.js +++ b/framework/Web/Javascripts/prado/validation3.js @@ -90,6 +90,15 @@ Object.extend(Prado.Validation, },
/**
+ * @return string first form ID.
+ */
+ getForm : function()
+ {
+ var keys = $H(this.managers).keys();
+ return keys[0];
+ },
+
+ /**
* Check if the validators are valid for a particular form (and group).
* The validators states will not be changed.
* The <tt>validate</tt> function should be called first.
diff --git a/framework/Web/UI/ActiveControls/TActiveControl.php b/framework/Web/UI/ActiveControls/TActiveControl.php index d289bab9..e61682d3 100644 --- a/framework/Web/UI/ActiveControls/TActiveControl.php +++ b/framework/Web/UI/ActiveControls/TActiveControl.php @@ -6,17 +6,43 @@ class TActiveControl extends TControl implements ICallbackEventHandler, IActiveControl
{
+ private $_clientSide;
+
public function __construct()
{
parent::__construct();
$this->setAdapter(new TActiveControlAdapter($this));
}
+
+ public function getClientSide()
+ {
+ if(is_null($this->_clientSide))
+ $this->_clientSide = $this->createClientSideOptions();
+ return $this->_clientSide;
+ }
+
+ protected function createClientSideOptions()
+ {
+ $client = new TCallbackClientSideOptions;
+ return $client;
+ }
public function raiseCallbackEvent($param)
{
- var_dump($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));
+ }
+
+ public function getCallbackReference()
+ {
+ // $formID = $this->getPage()->getForm()->getClientID();
+ // $this->getClientSide()->setValidationForm($formID);
+ return $this->getPage()->getClientScript()->getCallbackReference($this);
}
}
diff --git a/framework/Web/UI/ActiveControls/TActiveControlAdapter.php b/framework/Web/UI/ActiveControls/TActiveControlAdapter.php index 187b2cac..b95bdd37 100644 --- a/framework/Web/UI/ActiveControls/TActiveControlAdapter.php +++ b/framework/Web/UI/ActiveControls/TActiveControlAdapter.php @@ -15,7 +15,7 @@ class TActiveControlAdapter extends TControlAdapter if(!self::$_renderedPosts)
{
$options = TJavascript::encode($this->getPage()->getPostDataLoaders(),false);
- $script = "Prado.Callback.PostDataLoaders.concat({$options});";
+ $script = "Prado.CallbackRequest.PostDataLoaders.concat({$options});";
$this->getPage()->getClientScript()->registerEndScript(get_class($this), $script);
self::$_renderedPosts = true;
}
diff --git a/framework/Web/UI/ActiveControls/TActivePageAdapter.php b/framework/Web/UI/ActiveControls/TActivePageAdapter.php index ab042d54..2607fec2 100644 --- a/framework/Web/UI/ActiveControls/TActivePageAdapter.php +++ b/framework/Web/UI/ActiveControls/TActivePageAdapter.php @@ -22,6 +22,10 @@ */
class TActivePageAdapter extends TControlAdapter
{
+ const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
+ const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
+ const CALLBACK_ERROR_HEADER = 'X-PRADO-ERROR';
+
/**
* @var ICallbackEventHandler callback event handler.
*/
@@ -35,11 +39,9 @@ class TActivePageAdapter extends TControlAdapter */
private $_callbackClient;
/**
- * @var TCallbackResponse callback response handler.
+ * @var TCallbackEventParameter callback result.
*/
- private $_callbackResponse;
-
- private $_callbackEventResult;
+ private $_result;
/**
* Constructor, trap errors and exception to let the callback response
@@ -48,6 +50,7 @@ class TActivePageAdapter extends TControlAdapter public function __construct(TPage $control)
{
parent::__construct($control);
+ //$this->getApplication()->setResponse($this->getCallbackResponseHandler());
$this->trapCallbackErrorsExceptions();
}
@@ -62,36 +65,36 @@ class TActivePageAdapter extends TControlAdapter protected function trapCallbackErrorsExceptions()
{
- //TODO: How to trap the errors and exceptions and return them
- // as part of the response.
+ $this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
}
+ /**
+ * Render the callback response.
+ */
public function renderCallbackResponse($writer)
{
Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
$this->renderResponse($writer);
}
+ /**
+ * Renders the callback response by adding additional callback data and
+ * javascript actions in the header.
+ */
protected function renderResponse($writer)
{
- //var_dump(getallheaders());
- //TODO: How to render the response, it will contain 3 pieces of data
- // 1) The arbituary data returned to the client-side callback handler
- // 2) client-side function call statements
- // 3) Content body, which may need to be partitioned
-
- /*
- $response = $this->getCallbackResponseHandler();
- $response->writeClientScriptResponse($this->getCallbackClientHandler());
- $response->writeResponseData($this->getCallbackEventResult());
- $response->flush();
- */
+ $response = $this->getResponse();
+ $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();
}
/**
* Trys to find the callback event handler and raise its callback event.
- * @throws TInvalidCallbackRequestException if call back target is not
- * found.
+ * @throws TInvalidCallbackRequestException if call back target is not found.
* @throws TInvalidCallbackHandlerException if the requested target does not
* implement ICallbackEventHandler.
*/
@@ -100,7 +103,11 @@ class TActivePageAdapter extends TControlAdapter if(($callbackHandler=$this->getCallbackEventTarget())!==null)
{
if($callbackHandler instanceof ICallbackEventHandler)
- $callbackHandler->raiseCallbackEvent($this->getCallbackEventParameter());
+ {
+ $writer = $this->getResponse()->createHtmlWriter();
+ $this->_result = new TCallbackEventParameter($writer, $this->getCallbackEventParameter());
+ $callbackHandler->raiseCallbackEvent($this->_result);
+ }
else
throw new TInvalidCallbackHandlerException($callbackHandler->getUniqueID());
}
@@ -154,7 +161,6 @@ class TActivePageAdapter extends TControlAdapter $param = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
if(strlen($param) > 0)
$this->_callbackEventParameter=TJavascript::jsonDecode((string)$param);
- var_dump($param);
}
return $this->_callbackEventParameter;
}
@@ -187,23 +193,119 @@ class TActivePageAdapter extends TControlAdapter $this->_callbackClient = $handler;
}
+}
+
+/**
+ * TCallbackEventParameter class.
+ *
+ * The TCallbackEventParameter provides the parameter passed during the callback
+ * requestion in the {@link getParameter Parameter} property. The
+ * callback response response content (e.g. new HTML content) can be written to
+ * the {@link getOutput Output} property, which returns an instance of
+ * THtmlWriter. The response data (i.e., passing results back to the client-side
+ * callback handler function) can be set using {@link setData Data} property.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.ActiveControls
+ * @since 3.0
+ */
+class TCallbackEventParameter extends TEventParameter
+{
+ /**
+ * @var THtmlWriter output content.
+ */
+ private $_output;
+ /**
+ * @var mixed callback request parameter.
+ */
+ private $_parameter;
+ /**
+ * @var mixed callback response data.
+ */
+ private $_data;
+
+ /**
+ * Creates a new TCallbackEventParameter.
+ */
+ public function __construct($writer, $parameter)
+ {
+ $this->_output = $writer;
+ $this->_parameter = $parameter;
+ }
+
+ /**
+ * @return THtmlWriter holds the response content.
+ */
+ public function getOutput()
+ {
+ return $this->_output;
+ }
+
/**
- * Gets the callback response handler.
- * @return TCallbackResponse callback response
+ * @return mixed callback request parameter.
*/
- public function getCallbackResponseHandler()
+ public function getParameter()
{
- if(is_null($this->_callbackResponse))
- $this->_callbackResponse = new TCallbackResponse;
- return $this->_callbackResponse;
+ return $this->_parameter;
}
/**
- * @param TCallbackResponse new callback response handler.
+ * @param mixed callback response data.
*/
- public function setCallbackResponseHandler($handler)
+ public function setData($value)
{
- $this->_callbackResponse = $handler;
+ $this->_data = $value;
+ }
+
+ /**
+ * @return mixed callback response data.
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+}
+
+class TCallbackErrorHandler extends TErrorHandler
+{
+ protected function displayException($exception)
+ {
+ if($this->getApplication()->getMode()===TApplication::STATE_DEBUG)
+ {
+ $response = $this->getApplication()->getResponse();
+ $data = TJavascript::jsonEncode($this->getExceptionData($exception));
+ $response->appendHeader('HTTP/1.0 505 Internal Error');
+ $response->appendHeader(TActivePageAdapter::CALLBACK_ERROR_HEADER.': '.$data);
+ }
+ else
+ {
+ error_log("Error happened while processing an existing error:\n".$exception->__toString());
+ header('HTTP/1.0 500 Internal Error');
+ }
+ }
+
+ private function getExceptionData($exception)
+ {
+ $data['code']=$exception->getCode() > 0 ? $exception->getCode() : 505;
+ $data['file']=$exception->getFile();
+ $data['line']=$exception->getLine();
+ $data['trace']=$exception->getTrace();
+ if($exception instanceof TPhpErrorException)
+ {
+ // if PHP exception, we want to show the 2nd stack level context
+ // because the 1st stack level is of little use (it's in error handler)
+ if(isset($trace[0]) && isset($trace[0]['file']) && isset($trace[0]['line']))
+ {
+ $data['file']=$trace[0]['file'];
+ $data['line']=$trace[0]['line'];
+ }
+ }
+ $data['type']=get_class($exception);
+ $data['message']=$exception->getMessage();
+ $data['version']=$_SERVER['SERVER_SOFTWARE'].' '.Prado::getVersion();
+ $data['time']=@strftime('%Y-%m-%d %H:%M',time());
+ return $data;
}
}
diff --git a/framework/Web/UI/ActiveControls/TCallbackClientScript.php b/framework/Web/UI/ActiveControls/TCallbackClientScript.php index 550c88b5..10c0e638 100644 --- a/framework/Web/UI/ActiveControls/TCallbackClientScript.php +++ b/framework/Web/UI/ActiveControls/TCallbackClientScript.php @@ -63,10 +63,8 @@ class TCallbackClientScript */
public function callClientFunction($function, $params=null)
{
- if(!is_array($params) && $params !== null)
+ if(!is_array($params))
$params = array($params);
- else
- $params = array();
if(count($params) > 0)
{
diff --git a/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php b/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php new file mode 100644 index 00000000..dea1b4e1 --- /dev/null +++ b/framework/Web/UI/ActiveControls/TCallbackClientSideOptions.php @@ -0,0 +1,209 @@ +<?php
+/*
+ * Created on 1/05/2006
+ */
+
+class TClientSideOptions extends TComponent
+{
+ private $_options;
+
+ public function __construct()
+ {
+ $this->_options = Prado::createComponent('System.Collections.TMap');
+ }
+
+ protected function setFunction($name, $code)
+ {
+ $this->_options->add($name, $this->ensureFunction($code));
+ }
+
+ protected function getOption($name)
+ {
+ return $this->_options->itemAt($name);
+ }
+
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ protected function ensureFunction($javascript)
+ {
+ return $javascript;
+ }
+}
+
+/**
+ * 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.
+ *
+ * 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>CausesValidation</b> true to perform before callback request.
+ * - <b>ValidationGroup</b> validation grouping name.
+ */
+class TCallbackClientSideOptions extends TClientSideOptions
+{
+ protected function ensureFunction($javascript)
+ {
+ if(TJavascript::isFunction($javascript))
+ return $javascript;
+ else
+ {
+ $code = "function(request, result){ {$javascript} }";
+ return TJavascript::quoteFunction($code);
+ }
+ }
+
+ /**
+ * @return string javascript code for client-side onUninitialized event
+ */
+ public function getOnUninitialized()
+ {
+ return $this->getOption('onUninitialized');
+ }
+
+ /**
+ * @param string javascript code for client-side onUninitialized event.
+ */
+ public function setOnUninitialized($javascript)
+ {
+ $this->setFunction('onUninitialized', $javascript);
+ }
+
+ /**
+ * @return string javascript code for client-side onLoading event
+ */
+ public function getOnLoading()
+ {
+ return $this->getOption('onLoading');
+ }
+
+ /**
+ * @param string javascript code for client-side onLoading event.
+ */
+ public function setOnLoading($javascript)
+ {
+ $this->setFunction('onLoading', $javascript);
+ }
+
+ /**
+ * @return string javascript code for client-side onLoaded event
+ */
+ public function getOnLoaded()
+ {
+ return $this->getOption('onLoaded');
+ }
+
+ /**
+ * @param string javascript code for client-side onLoaded event.
+ */
+ public function setOnLoaded($javascript)
+ {
+ $this->setFunction('onLoaded', $javascript);
+ }
+ /**
+ * @return string javascript code for client-side onInteractive event
+ */
+ public function getOnInteractive()
+ {
+ return $this->getOption('onInteractive');
+ }
+
+ /**
+ * @param string javascript code for client-side onInteractive event.
+ */
+ public function setonInteractive($javascript)
+ {
+ $this->setFunction('onInteractive', $javascript);
+ }
+ /**
+ * @return string javascript code for client-side onComplete event
+ */
+ public function getOnComplete()
+ {
+ return $this->getOption('onComplete');
+ }
+
+ /**
+ * @param string javascript code for client-side onComplete event.
+ */
+ public function setOnComplete($javascript)
+ {
+ $this->setFunction('onComplete', $javascript);
+ }
+ /**
+ * @return string javascript code for client-side onSuccess event
+ */
+ public function getOnSuccess()
+ {
+ return $this->getOption('onSuccess');
+ }
+
+ /**
+ * @param string javascript code for client-side onSuccess event.
+ */
+ public function setOnSuccess($javascript)
+ {
+ $this->setFunction('onSuccess', $javascript);
+ }
+
+ /**
+ * @return string javascript code for client-side onFailure event
+ */
+ public function getOnFailure()
+ {
+ return $this->getOption('onFailure');
+ }
+
+ /**
+ * @param string javascript code for client-side onFailure event.
+ */
+ public function setOnFailure($javascript)
+ {
+ $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 $this->getOption('ValidationGroup');
+ }
+
+ public function setValidationGroup($value)
+ {
+ $this->getOptions()->add('ValidationGroup', $value);
+ }
+
+ public function getValidationForm()
+ {
+ return $this->getOption('Form');
+ }
+
+ public function setValidationForm($value)
+ {
+ $this->getOptions()->add('Form', $value);
+ }
+}
+
+?>
diff --git a/framework/Web/UI/ActiveControls/TCallbackResponse.php b/framework/Web/UI/ActiveControls/TCallbackResponse.php index bda4e916..a05c20c7 100644 --- a/framework/Web/UI/ActiveControls/TCallbackResponse.php +++ b/framework/Web/UI/ActiveControls/TCallbackResponse.php @@ -11,8 +11,7 @@ class TCallbackResponse extends THttpResponse
{
- const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
- const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
+
}
diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php index 94ef19b6..46203b0b 100644 --- a/framework/Web/UI/TClientScriptManager.php +++ b/framework/Web/UI/TClientScriptManager.php @@ -164,6 +164,21 @@ class TClientScriptManager extends TApplicationComponent } } + public function getCallbackReference($callbackHandler, $options=null) + { + $options = !is_array($options) ? array() : $options; + $class = new TReflectionClass($callbackHandler); + if($class->hasMethod('getClientSide')) + { + $clientSide = $callbackHandler->getClientSide(); + $options = array_merge($options, $clientSide->getOptions()->toArray()); + } + $optionString = TJavascript::encode($options); + $this->registerPradoScriptInternal('ajax'); + $id = $callbackHandler->getUniqueID(); + return "new Prado.CallbackRequest('{$id}',{$optionString})"; + } + /** * Registers postback javascript for a control. * @param string javascript class responsible for the control being registered for postback diff --git a/framework/Web/UI/TControl.php b/framework/Web/UI/TControl.php index 1b5394ba..1db01df6 100644 --- a/framework/Web/UI/TControl.php +++ b/framework/Web/UI/TControl.php @@ -1215,9 +1215,6 @@ class TControl extends TApplicationComponent implements IRenderable, IBindable protected function preRenderRecursive()
{
$this->autoDataBindProperties();
-
- if($this->getEnabled() && $this instanceof IPostBackDataHandler)
- $this->getPage()->registerPostDataLoader($this);
if($this->getVisible(false))
{
@@ -1238,6 +1235,10 @@ class TControl extends TApplicationComponent implements IRenderable, IBindable }
}
$this->_stage=self::CS_PRERENDERED;
+
+
+ if($this->getEnabled() && $this instanceof IPostBackDataHandler)
+ $this->getPage()->registerPostDataLoader($this);
}
/**
diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php index 31c80320..c72cdd03 100644 --- a/framework/Web/UI/TPage.php +++ b/framework/Web/UI/TPage.php @@ -323,25 +323,6 @@ class TPage extends TTemplateControl }
/**
- * Gets the callback response handler that permits changing the callback
- * response headers and contents.
- * @return TCallbackResponse callback response handler.
- */
- public function getCallbackResponse()
- {
- return $this->getAdapter()->getCallbackResponseHandler();
- }
-
- /**
- * Set a new callback respond handler.
- * @param TCallbackResponse a different callback response handler.
- */
- public function setCallbackResponse($responder)
- {
- $this->getAdapter()->setCallbackResponseHandler($responder);
- }
-
- /**
* Gets the callback client script handler that allows javascript functions
* to be executed during the callback response.
* @return TCallbackClientScript interface to client-side javascript code.
diff --git a/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page b/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page index 63bc7f24..c20e810b 100644 --- a/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page +++ b/tests/FunctionalTests/features/protected/pages/ActiveControls/ActiveControl.page @@ -1,16 +1,21 @@ <com:TContent ID="Content">
<com:TClientScript UsingPradoScripts="ajax" />
- <com:TActiveControl id="control1" />
- <com:TTextBox />
+ <com:TActiveControl id="control1"
+ ClientSide.CausesValidation="false"
+ ClientSide.OnSuccess="Logger.info('result:'+result)" />
+ <com:TTextBox id="text1" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="text1"
+ ErrorMessage="*" />
<com:TCheckBoxList>
<com:TListItem Text="One" />
<com:TListItem Text="Two" />
</com:TCheckBoxList>
- <com:TButton id="button1" Text="Submit" />
+ <com:TButton id="button1" Text="Submit" CausesValidation="false" />
<script>
Event.observe("<%= $this->button1->ClientID %>", "click", function(event)
{
- new Prado.Callback("<%= $this->control1->UniqueID %>", 1);
+ <%= $this->control1->CallbackReference %>
Event.stop(event);
});
</script>
|