From 15f6747485b5912f657c2c0fce8f41c01c70d2ad Mon Sep 17 00:00:00 2001 From: "ctrlaltca@gmail.com" <> Date: Fri, 24 Jun 2011 22:12:36 +0000 Subject: merged in the patch for progressive rendering from #235; unit tests doesn't evidence any regression, but of course more proper testing is needed --- framework/TApplication.php | 6 +- framework/Web/THttpResponse.php | 78 +++++++++++++++++++--- .../UI/ActiveControls/TActiveControlAdapter.php | 10 ++- framework/Web/UI/ActiveControls/TDraggable.php | 19 ++++-- framework/Web/UI/ActiveControls/TDropContainer.php | 13 +++- framework/Web/UI/TClientScriptManager.php | 67 +++++++++++++++++-- framework/Web/UI/TForm.php | 28 ++++---- framework/Web/UI/THtmlWriter.php | 3 +- framework/Web/UI/TPage.php | 27 +++++++- framework/Web/UI/WebControls/TBaseValidator.php | 3 +- framework/Web/UI/WebControls/THtmlArea.php | 18 +++-- 11 files changed, 227 insertions(+), 45 deletions(-) diff --git a/framework/TApplication.php b/framework/TApplication.php index b481f12a..1fc9485a 100644 --- a/framework/TApplication.php +++ b/framework/TApplication.php @@ -1252,10 +1252,11 @@ class TApplication extends TComponent /** * Flushes output to client side. + * @param boolean whether to continue buffering after flush if buffering was active */ - public function flushOutput() + public function flushOutput($continueBuffering = true) { - $this->getResponse()->flush(); + $this->getResponse()->flush($continueBuffering); } /** @@ -1264,6 +1265,7 @@ class TApplication extends TComponent */ public function onEndRequest() { + $this->flushOutput(false); // flush all remaining content in the buffer $this->saveGlobals(); // save global state $this->raiseEvent('OnEndRequest',$this,null); } diff --git a/framework/Web/THttpResponse.php b/framework/Web/THttpResponse.php index 50ac4d7d..07d11a2c 100644 --- a/framework/Web/THttpResponse.php +++ b/framework/Web/THttpResponse.php @@ -116,6 +116,14 @@ class THttpResponse extends TModule implements ITextWriter * @var THttpResponseAdapter adapter. */ private $_adapter; + /** + * @var boolean whether http response header has been sent + */ + private $_httpHeaderSent; + /** + * @var boolean whether content-type header has been sent + */ + private $_contentTypeHeaderSent; /** * Destructor. @@ -203,6 +211,8 @@ class THttpResponse extends TModule implements ITextWriter */ public function setContentType($type) { + if ($this->_contentTypeHeaderSent) + throw new Exception('Unable to alter content-type as it has been already sent'); $this->_contentType = $type; } @@ -268,6 +278,8 @@ class THttpResponse extends TModule implements ITextWriter */ public function setStatusCode($status, $reason=null) { + if ($this->_httpHeaderSent) + throw new Exception('Unable to alter response as HTTP header already sent'); $status=TPropertyValue::ensureInteger($status); if(isset(self::$HTTP_STATUS_CODES[$status])) { $this->_reason=self::$HTTP_STATUS_CODES[$status]; @@ -308,6 +320,9 @@ class THttpResponse extends TModule implements ITextWriter */ public function write($str) { + // when starting output make sure we send the headers first + if (!$this->_bufferOutput and !$this->_httpHeaderSent) + $this->ensureHeadersSent(); echo $str; } @@ -435,36 +450,79 @@ class THttpResponse extends TModule implements ITextWriter /** * Flush the response contents and headers. */ - public function flush() + public function flush($continueBuffering = true) { if($this->getHasAdapter()) - $this->_adapter->flushContent(); + $this->_adapter->flushContent($continueBuffering); else - $this->flushContent(); + $this->flushContent($continueBuffering); + } + + /** + * Ensures that HTTP response and content-type headers are sent + */ + public function ensureHeadersSent() + { + $this->ensureHttpHeaderSent(); + $this->ensureContentTypeHeaderSent(); } /** * Outputs the buffered content, sends content-type and charset header. * This method is used internally. Please use {@link flush} instead. + * @param boolean whether to continue buffering after flush if buffering was active */ - public function flushContent() + public function flushContent($continueBuffering = true) { Prado::trace("Flushing output",'System.Web.THttpResponse'); - $this->sendHttpHeader(); - $this->sendContentTypeHeader(); - if($this->_bufferOutput) - ob_flush(); + $this->ensureHeadersSent(); + if($this->_bufferOutput) + { + // avoid forced send of http headers (ob_flush() does that) if there's no output yet + if (ob_get_length()>0) + { + if (!$continueBuffering) + { + $this->_bufferOutput = false; + ob_end_flush(); + } + else + ob_flush(); + flush(); + } + } + else + flush(); + } + + /** + * Ensures that the HTTP header with the status code and status reason are sent + */ + protected function ensureHttpHeaderSent() + { + if (!$this->_httpHeaderSent) + $this->sendHttpHeader(); } /** * Send the HTTP header with the status code (defaults to 200) and status reason (defaults to OK) */ - protected function sendHttpHeader () + protected function sendHttpHeader() { if (($version=$this->getRequest()->getHttpProtocolVersion())==='') header (' ', true, $this->_status); else header($version.' '.$this->_status.' '.$this->_reason, true, $this->_status); + $this->_httpHeaderSent = true; + } + + /** + * Ensures that the HTTP header with the status code and status reason are sent + */ + protected function ensureContentTypeHeaderSent() + { + if (!$this->_contentTypeHeaderSent) + $this->sendContentTypeHeader(); } /** @@ -484,6 +542,8 @@ class THttpResponse extends TModule implements ITextWriter if($charset==='') $charset = self::DEFAULT_CHARSET; $this->appendHeader('Content-Type: '.$contentType.';charset='.$charset); + + $this->_contentTypeHeaderSent = true; } /** diff --git a/framework/Web/UI/ActiveControls/TActiveControlAdapter.php b/framework/Web/UI/ActiveControls/TActiveControlAdapter.php index e8f947af..99c5e71e 100644 --- a/framework/Web/UI/ActiveControls/TActiveControlAdapter.php +++ b/framework/Web/UI/ActiveControls/TActiveControlAdapter.php @@ -75,6 +75,15 @@ class TActiveControlAdapter extends TControlAdapter $this->_activeControlType = $type; } + /** + * Publish the ajax script + */ + public function onPreRender($param) + { + parent::onPreRender($param); + $this->getPage()->getClientScript()->registerPradoScript('ajax'); + } + /** * Renders the callback client scripts. */ @@ -93,7 +102,6 @@ class TActiveControlAdapter extends TControlAdapter $key = 'Prado.CallbackRequest.addPostLoaders'; if(!$cs->isEndScriptRegistered($key)) { - $cs->registerPradoScript('ajax'); $data = $this->getPage()->getPostDataLoaders(); if(count($data) > 0) { diff --git a/framework/Web/UI/ActiveControls/TDraggable.php b/framework/Web/UI/ActiveControls/TDraggable.php index 34d8c548..10e78b9b 100755 --- a/framework/Web/UI/ActiveControls/TDraggable.php +++ b/framework/Web/UI/ActiveControls/TDraggable.php @@ -136,6 +136,20 @@ class TDraggable extends TPanel $this->setViewState('Constraint', TPropertyValue::ensureEnum($value, 'TDraggableConstraint'), TDraggableConstraint::None); } + /** + * Registers clientscripts + * + * This method overrides the parent implementation and is invoked before render. + * @param mixed event parameter + */ + public function onPreRender($param) + { + parent::onPreRender($param); + if ($this->getGhosting()==TDraggableGhostingOptions::SuperGhosting) + $cs->registerPradoScript('dragdropextra'); + else + $cs->registerPradoScript('dragdrop'); + } /** * Ensure that the ID attribute is rendered and registers the javascript code @@ -145,11 +159,6 @@ class TDraggable extends TPanel { parent::addAttributesToRender($writer); $writer->addAttribute('id',$this->getClientID()); - $cs=$this->getPage()->getClientScript(); - if ($this->getGhosting()==TDraggableGhostingOptions::SuperGhosting) - $cs->registerPradoScript('dragdropextra'); - else - $cs->registerPradoScript('dragdrop'); $options=TJavascript::encode($this->getPostBackOptions()); $class=$this->getClientClassName(); $code="new {$class}('{$this->getClientId()}', {$options}) "; diff --git a/framework/Web/UI/ActiveControls/TDropContainer.php b/framework/Web/UI/ActiveControls/TDropContainer.php index 915aa8f9..e6933147 100755 --- a/framework/Web/UI/ActiveControls/TDropContainer.php +++ b/framework/Web/UI/ActiveControls/TDropContainer.php @@ -172,6 +172,17 @@ class TDropContainer extends TPanel implements IActiveControl, ICallbackEventHan return 'Prado.WebUI.DropContainer'; } + /** + * Registers clientscripts + * + * This method overrides the parent implementation and is invoked before render. + * @param mixed event parameter + */ + public function onPreRender($param) + { + parent::onPreRender($param); + $this->getPage()->getClientScript()->registerPradoScript('dragdrop'); + } /** * Ensure that the ID attribute is rendered and registers the javascript code @@ -182,8 +193,6 @@ class TDropContainer extends TPanel implements IActiveControl, ICallbackEventHan parent::addAttributesToRender($writer); $writer->addAttribute('id',$this->getClientID()); - $this->getPage()->getClientScript()->registerPradoScript('dragdrop'); - $this->getActiveControl()->registerCallbackClientScript( $this->getClientClassName(), $this->getPostBackOptions()); } diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php index 8f246d3d..eec347eb 100644 --- a/framework/Web/UI/TClientScriptManager.php +++ b/framework/Web/UI/TClientScriptManager.php @@ -82,6 +82,12 @@ class TClientScriptManager extends TApplicationComponent */ private static $_pradoPackages; + private $_renderedHiddenFields; + + private $_renderedScriptFiles; + + private $_renderedPradoScripts; + /** * Constructor. * @param TPage page that owns this client script manager @@ -118,6 +124,7 @@ class TClientScriptManager extends TApplicationComponent */ private function registerPradoScriptInternal($name) { + // $this->checkIfNotInRender(); if(!isset($this->_registeredPradoScripts[$name])) { if(self::$_pradoScripts === null) @@ -149,9 +156,11 @@ class TClientScriptManager extends TApplicationComponent * Renders the HTML tags for PRADO js files * @param THtmlWriter writer */ - protected function renderPradoScripts($writer) + protected function renderPradoScriptsInt($writer, $initial) { - if(($packages=array_keys($this->_registeredPradoScripts))!==array()) + if($initial) $this->_renderedPradoScripts = array(); + $addedScripts = array_diff($this->_registeredPradoScripts,$this->_renderedPradoScripts); + if(($packages=array_keys($addedScripts))!==array()) { if (Prado::getApplication()->getMode()!==TApplicationMode::Debug) { @@ -178,6 +187,7 @@ class TClientScriptManager extends TApplicationComponent } $writer->write(TJavaScript::renderScriptFiles($packagesUrl)); } + $this->_renderedPradoScripts = $this->_registeredPradoScripts; } } @@ -420,6 +430,7 @@ class TClientScriptManager extends TApplicationComponent */ public function registerHeadScriptFile($key,$url) { + $this->checkIfNotInRender(); $this->_headScriptFiles[$key]=$url; $params=func_get_args(); @@ -433,6 +444,7 @@ class TClientScriptManager extends TApplicationComponent */ public function registerHeadScript($key,$script) { + $this->checkIfNotInRender(); $this->_headScripts[$key]=$script; $params=func_get_args(); @@ -446,6 +458,7 @@ class TClientScriptManager extends TApplicationComponent */ public function registerScriptFile($key,$url) { + $this->checkIfNotInRender(); $this->_scriptFiles[$key]=$url; $params=func_get_args(); @@ -459,6 +472,7 @@ class TClientScriptManager extends TApplicationComponent */ public function registerBeginScript($key,$script) { + $this->checkIfNotInRender(); $this->_beginScripts[$key]=$script; $params=func_get_args(); @@ -486,6 +500,7 @@ class TClientScriptManager extends TApplicationComponent */ public function registerHiddenField($name,$value) { + $this->checkIfNotInRender(); $this->_hiddenFields[$name]=$value; $params=func_get_args(); @@ -621,14 +636,30 @@ class TClientScriptManager extends TApplicationComponent $writer->write(TJavaScript::renderScriptBlocks($this->_headScripts)); } + public function renderScriptFilesBegin($writer) + { + $this->renderScriptFilesInt($writer,true); + } + + public function renderScriptFilesEnd($writer) + { + $this->renderScriptFilesInt($writer,false); + } + /** * @param THtmlWriter writer for the rendering purpose */ - public function renderScriptFiles($writer) + public function renderScriptFilesInt($writer, $initial) { - $this->renderPradoScripts($writer); + if ($initial) $this->_renderedScriptFiles = array(); + $this->renderPradoScriptsInt($writer, $initial); if(!empty($this->_scriptFiles)) - $writer->write(TJavaScript::renderScriptFiles($this->_scriptFiles)); + { + $addedScripts = array_diff($this->_scriptFiles,$this->_renderedScriptFiles); + if (count($addedScripts)>0) + $writer->write(TJavaScript::renderScriptFiles($addedScripts)); + $this->_renderedScriptFiles = $this->_scriptFiles; + } } /** @@ -647,14 +678,26 @@ class TClientScriptManager extends TApplicationComponent $writer->write(TJavaScript::renderScriptBlocks($this->_endScripts)); } + public function renderHiddenFieldsBegin($writer) + { + $this->renderHiddenFieldsInt($writer,true); + } + + public function renderHiddenFieldsEnd($writer) + { + $this->renderHiddenFieldsInt($writer,false); + } + /** * @param THtmlWriter writer for the rendering purpose */ - public function renderHiddenFields($writer) - { + protected function renderHiddenFieldsInt($writer, $initial) + { + if ($initial) $this->_renderedHiddenFields = array(); $str=''; foreach($this->_hiddenFields as $name=>$value) { + if (in_array($name,$this->_renderedHiddenFields)) continue; $id=strtr($name,':','_'); if(is_array($value)) { @@ -665,10 +708,20 @@ class TClientScriptManager extends TApplicationComponent { $str.='\n"; } + $this->_renderedHiddenFields[] = $name; } if($str!=='') $writer->write("
\n"); } + + /** + * Checks whether page rendering has not begun yet + */ + protected function checkIfNotInRender() + { + if ($form = $this->_page->InFormRender) + throw new Exception('Operation invalid when page is already rendering'); + } } /** diff --git a/framework/Web/UI/TForm.php b/framework/Web/UI/TForm.php index 561b59cb..f1e8df5a 100644 --- a/framework/Web/UI/TForm.php +++ b/framework/Web/UI/TForm.php @@ -4,7 +4,7 @@ * * @author Qiang Xue