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