From fedddfd60edb9cfe5bb5a90745ad7d8b963661ac Mon Sep 17 00:00:00 2001 From: "ctrlaltca@gmail.com" <> Date: Sat, 16 Jul 2011 09:28:55 +0000 Subject: applied prado-lazyload-in-callbacks-patch (ticket #348) --- .../source/prado/activecontrols/ajax3.js | 433 ++++++++++++++++++++- framework/Web/TAssetManager.php | 2 +- .../Web/UI/ActiveControls/TActivePageAdapter.php | 32 ++ framework/Web/UI/TClientScriptManager.php | 65 ++++ framework/Web/UI/WebControls/THtmlArea.php | 4 +- 5 files changed, 517 insertions(+), 19 deletions(-) diff --git a/framework/Web/Javascripts/source/prado/activecontrols/ajax3.js b/framework/Web/Javascripts/source/prado/activecontrols/ajax3.js index d9b5bdfa..b0e4e31e 100644 --- a/framework/Web/Javascripts/source/prado/activecontrols/ajax3.js +++ b/framework/Web/Javascripts/source/prado/activecontrols/ajax3.js @@ -26,32 +26,53 @@ Object.extend(Prado.AjaxRequest.prototype, if (event == 'Complete') { - var redirectUrl = this.getBodyContentPart(Prado.CallbackRequest.REDIRECT_HEADER); - if(redirectUrl) + var redirectUrl = this.getBodyContentPart(Prado.CallbackRequest.REDIRECT_HEADER); + if (redirectUrl) document.location.href = redirectUrl; - if ((this.getHeader('Content-type') || '').match(/^text\/javascript/i)) - { - try + if ((this.getHeader('Content-type') || '').match(/^text\/javascript/i)) + { + try { - json = eval('(' + transport.responseText + ')'); - }catch (e) + json = eval('(' + transport.responseText + ')'); + } + catch (e) { if(typeof(json) == "string") json = Prado.CallbackRequest.decode(result); } - } + } - try - { - Prado.CallbackRequest.updatePageState(this,transport); - Ajax.Responders.dispatch('on' + transport.status, this, transport, json); - Prado.CallbackRequest.dispatchActions(transport,this.getBodyDataPart(Prado.CallbackRequest.ACTION_HEADER)); + try + { + Prado.CallbackRequest.updatePageState(this,transport); + Prado.CallbackRequest.checkHiddenFields(this,transport); + var obj = this; + Prado.CallbackRequest.loadAssets(this,transport, function() - (this.options['on' + this.transport.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(this, json); - } catch (e) { + { + try + { + Ajax.Responders.dispatch('on' + transport.status, obj, transport, json); + Prado.CallbackRequest.dispatchActions(transport,obj.getBodyDataPart(Prado.CallbackRequest.ACTION_HEADER)); + + ( + obj.options['on' + obj.transport.status] + || + obj.options['on' + (obj.success() ? 'Success' : 'Failure')] + || + Prototype.emptyFunction + ) (obj, json); + } + catch (e) + { + obj.dispatchException(e); + } + } + ); + } + catch (e) + { this.dispatchException(e); } } @@ -150,6 +171,18 @@ Object.extend(Prado.CallbackRequest, * Page state header name. */ PAGESTATE_HEADER : 'X-PRADO-PAGESTATE', + /** + * Script list header name. + */ + SCRIPTLIST_HEADER : 'X-PRADO-SCRIPTLIST', + /** + * Stylesheet list header name. + */ + STYLESHEETLIST_HEADER : 'X-PRADO-STYLESHEETLIST', + /** + * Hidden field list header name. + */ + HIDDENFIELDLIST_HEADER : 'X-PRADO-HIDDENFIELDLIST', REDIRECT_HEADER : 'X-PRADO-REDIRECT', @@ -344,6 +377,181 @@ Object.extend(Prado.CallbackRequest, //Logger.warn('current request ' + self.currentRequest.id); }, + /* + * Checks which scripts are used by the response and ensures they're loaded + */ + loadScripts : function(request, transport, callback) + { + var self = Prado.CallbackRequest; + var data = request.getBodyContentPart(self.SCRIPTLIST_HEADER); + if (!this.ScriptsToLoad) this.ScriptsToLoad = new Array(); + this.ScriptLoadFinishedCallback = callback; + if (typeof(data) == "string" && data.length > 0) + { + json = Prado.CallbackRequest.decode(data); + if(typeof(json) != "object") + Logger.warn("Invalid script list:"+data); + else + for(var key in json) + if (/^\d+$/.test(key)) + { + var url = json[key]; + if (!Prado.ScriptManager.isAssetLoaded(url)) + this.ScriptsToLoad.push(url); + } + } + this.loadNextScript(); + }, + + loadNextScript: function() + { + var done = (!this.ScriptsToLoad || (this.ScriptsToLoad.length==0)); + if (!done) + { + var url = this.ScriptsToLoad.shift(); var obj = this; + if ( + Prado.ScriptManager.ensureAssetIsLoaded(url, + function() { + obj.loadNextScript(); + } + ) + ) + this.loadNextScript(); + } + else + { + if (this.ScriptLoadFinishedCallback) + { + var cb = this.ScriptLoadFinishedCallback; + this.ScriptLoadFinishedCallback = null; + cb(); + } + } + }, + + loadStyleSheetsAsync : function(request, transport) + { + var self = Prado.CallbackRequest; + var data = request.getBodyContentPart(self.STYLESHEETLIST_HEADER); + if (typeof(data) == "string" && data.length > 0) + { + json = Prado.CallbackRequest.decode(data); + if(typeof(json) != "object") + Logger.warn("Invalid stylesheet list:"+data); + else + for(var key in json) + if (/^\d+$/.test(key)) + Prado.StyleSheetManager.ensureAssetIsLoaded(json[key],null); + } + }, + + loadStyleSheets : function(request, transport, callback) + { + var self = Prado.CallbackRequest; + var data = request.getBodyContentPart(self.STYLESHEETLIST_HEADER); + if (!this.StyleSheetsToLoad) this.StyleSheetsToLoad = new Array(); + this.StyleSheetLoadFinishedCallback = callback; + if (typeof(data) == "string" && data.length > 0) + { + json = Prado.CallbackRequest.decode(data); + if(typeof(json) != "object") + Logger.warn("Invalid stylesheet list:"+data); + else + for(var key in json) + if (/^\d+$/.test(key)) + { + var url = json[key]; + if (!Prado.StyleSheetManager.isAssetLoaded(url)) + this.StyleSheetsToLoad.push(url); + } + } + this.loadNextStyleSheet(); + }, + + loadNextStyleSheet: function() + { + var done = (!this.StyleSheetsToLoad || (this.StyleSheetsToLoad.length==0)); + if (!done) + { + var url = this.StyleSheetsToLoad.shift(); var obj = this; + if ( + Prado.StyleSheetManager.ensureAssetIsLoaded(url, + function() { + obj.loadNextStyleSheet(); + } + ) + ) + this.loadNextStyleSheet(); + } + else + { + if (this.StyleSheetLoadFinishedCallback) + { + var cb = this.StyleSheetLoadFinishedCallback; + this.StyleSheetLoadFinishedCallback = null; + cb(); + } + } + }, + + /* + * Checks which assets are used by the response and ensures they're loaded + */ + loadAssets : function(request, transport, callback) + { + /* + + ! This is the callback-based loader for stylesheets, which loads them one-by-one, and + ! waits for all of them to be loaded before loading scripts and processing the rest of + ! the callback. + ! + ! That however is not neccessary, as stylesheets can be loaded asynchronously too. + ! + ! I leave this code here for the case that this turns out to be a compatibility issue + ! (for ex. I can imagine some scripts trying to access stylesheet properties and such) + ! so if need can be reactivated. If you do so, comment out the async stylesheet loader below! + + var obj = this; + this.loadStyleSheets(request,transport, function() { + obj.loadScripts(request,transport,callback); + }); + + */ + + this.loadStyleSheetsAsync(request,transport); + + this.loadScripts(request,transport,callback); + }, + + checkHiddenField: function(name, value) + { + var id = name.replace(':','_'); + if (!document.getElementById(id)) + { + var field = document.createElement('input'); + field.setAttribute('type','hidden'); + field.id = id; + field.name = name; + field.value = value; + document.body.appendChild(field); + } + }, + + checkHiddenFields : function(request, transport) + { + var self = Prado.CallbackRequest; + var data = request.getBodyContentPart(self.HIDDENFIELDLIST_HEADER); + if (typeof(data) == "string" && data.length > 0) + { + json = Prado.CallbackRequest.decode(data); + if(typeof(json) != "object") + Logger.warn("Invalid hidden field list:"+data); + else + for(var key in json) + this.checkHiddenField(key,json[key]); + } + }, + /** * Updates the page state. It will update only if EnablePageStateUpdate and * HasPriority options are both true. @@ -722,3 +930,194 @@ Prado.Callback = function(UniqueID, parameter, onSuccess, options) request.dispatch(); return false; }; + + + +/** + * Asset manager classes for lazy loading of scripts and stylesheets + * @author Gabor Berczi (gabor.berczi@devworx.hu) + */ + +if (typeof(Prado.AssetManagerClass)=="undefined") { + + Prado.AssetManagerClass = Class.create(); + Prado.AssetManagerClass.prototype = { + + initialize: function() { + this.loadedAssets = new Array(); + this.discoverLoadedAssets(); + }, + + + /** + * Detect which assets are already loaded by page markup. + * This is done by looking up all elements and registering the values of their src attributes. + */ + discoverLoadedAssets: function() { + + // wait until document has finished loading to avoid javascript errors + if (!document.body) return; + + var assets = this.findAssetUrlsInMarkup(); + for(var i=0;i element in page header + var asset = this.createAssetElement(url); + + if (callback) + { + asset.onreadystatechange = this.createAssetReadyStateChangeFunction(url, asset, callback, false); + asset.onload = this.createAssetReadyStateChangeFunction(url, asset, callback, true); + } + + var head = document.getElementsByTagName('head')[0]; + head.appendChild(asset); + + // mark this asset as loaded + this.markAssetAsLoaded(url); + + return (callback!=false); + }, + + /** + * Check whether a asset is loaded into the page, and if itsn't, load it now + * @param string url of the asset to check/load + * @return boolean returns true if asset is already loaded, or false, if loading has just started. callback will be called when loading has finished. + */ + ensureAssetIsLoaded: function(url, callback) { + url = this.makeFullUrl(url); + if (this.loadedAssets.indexOf(url)==-1) + { + this.startAssetLoad(url,callback); + return false; + } + else + return true; + } + + } + +}; + + Prado.ScriptManagerClass = Class.extend(Prado.AssetManagerClass, { + + findAssetUrlsInMarkup: function() { + var urls = new Array(); + var scripts = document.getElementsByTagName('script'); + for(var i=0;i0)) + urls.push(href); + } + return urls; + }, + + createAssetElement: function(url) { + var asset = document.createElement('link'); + asset.rel = 'stylesheet'; + asset.media = 'screen'; + asset.setAttribute('type', 'text/css'); + asset.href = url; +// asset.async = false; // HTML5 only + return asset; + } + + }); + + if (typeof(Prado.ScriptManager)=="undefined") Prado.ScriptManager = new Prado.ScriptManagerClass(); + if (typeof(Prado.StyleSheetManager)=="undefined") Prado.StyleSheetManager = new Prado.StyleSheetManagerClass(); + + // make sure we scan for loaded scripts again when the page has been loaded + var discover = function() { + Prado.ScriptManager.discoverLoadedAssets(); + Prado.StyleSheetManager.discoverLoadedAssets(); + } + if (window.attachEvent) window.attachEvent('onload', discover); + else if (window.addEventListener) window.addEventListener('load', discover, false); + diff --git a/framework/Web/TAssetManager.php b/framework/Web/TAssetManager.php index 61634910..7f3d4986 100644 --- a/framework/Web/TAssetManager.php +++ b/framework/Web/TAssetManager.php @@ -182,7 +182,7 @@ class TAssetManager extends TModule * @return array List of published assets * @since 3.1.6 */ - protected function getPublished() + public function getPublished() { return $this->_published; } diff --git a/framework/Web/UI/ActiveControls/TActivePageAdapter.php b/framework/Web/UI/ActiveControls/TActivePageAdapter.php index f8abd3ed..c5008dfe 100644 --- a/framework/Web/UI/ActiveControls/TActivePageAdapter.php +++ b/framework/Web/UI/ActiveControls/TActivePageAdapter.php @@ -3,6 +3,7 @@ * TActivePageAdapter, TCallbackErrorHandler and TInvalidCallbackException class file. * * @author Wei Zhuo + * @author Gabor Berczi (lazyload additions & progressive rendering) * @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2011 PradoSoft * @license http://www.pradosoft.com/license/ @@ -23,6 +24,7 @@ Prado::using('System.Web.UI.ActiveControls.TCallbackEventParameter'); * Callback request handler. * * @author Wei Zhuo + * @author Gabor Berczi (lazyload additions & progressive rendering) * @version $Id$ * @package System.Web.UI.ActiveControls * @since 3.1 @@ -45,6 +47,18 @@ class TActivePageAdapter extends TControlAdapter * Callback page state header name. */ const CALLBACK_PAGESTATE_HEADER = 'X-PRADO-PAGESTATE'; + /** + * Script list header name. + */ + const CALLBACK_SCRIPTLIST_HEADER = 'X-PRADO-SCRIPTLIST'; + /** + * Stylesheet list header name. + */ + const CALLBACK_STYLESHEETLIST_HEADER = 'X-PRADO-STYLESHEETLIST'; + /** + * Hidden field list header name. + */ + const CALLBACK_HIDDENFIELDLIST_HEADER = 'X-PRADO-HIDDENFIELDLIST'; /** * Callback redirect url header name. @@ -189,6 +203,24 @@ class TActivePageAdapter extends TControlAdapter $actions = TJavaScript::jsonEncode($executeJavascript); $this->appendContentPart($response, self::CALLBACK_ACTION_HEADER, $actions); //$response->appendHeader(self::CALLBACK_ACTION_HEADER.': '.$actions); + + + $cs = $this->Page->getClientScript(); + + // collect all stylesheet file references + $stylesheets = $cs->getStyleSheetUrls(); + if (count($stylesheets)>0) + $this->appendContentPart($response, self::CALLBACK_STYLESHEETLIST_HEADER, TJavaScript::jsonEncode($stylesheets)); + + // collect all script file references + $scripts = $cs->getScriptUrls(); + if (count($scripts)>0) + $this->appendContentPart($response, self::CALLBACK_SCRIPTLIST_HEADER, TJavaScript::jsonEncode($scripts)); + + // collect all hidden field references + $fields = $cs->getHiddenFields(); + if (count($fields)>0) + $this->appendContentPart($response, self::CALLBACK_HIDDENFIELDLIST_HEADER, TJavaScript::jsonEncode($fields)); } /** diff --git a/framework/Web/UI/TClientScriptManager.php b/framework/Web/UI/TClientScriptManager.php index eec347eb..677daa9b 100644 --- a/framework/Web/UI/TClientScriptManager.php +++ b/framework/Web/UI/TClientScriptManager.php @@ -3,6 +3,7 @@ * TClientScriptManager and TClientSideOptions class file. * * @author Qiang Xue + * @author Gabor Berczi (lazyload additions & progressive rendering) * @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2011 PradoSoft * @license http://www.pradosoft.com/license/ @@ -16,6 +17,7 @@ * TClientScriptManager manages javascript and CSS stylesheets for a page. * * @author Qiang Xue + * @author Gabor Berczi (lazyload additions & progressive rendering) * @version $Id$ * @package System.Web.UI * @since 3.0 @@ -107,6 +109,16 @@ class TClientScriptManager extends TApplicationComponent || count($this->_headScriptFiles) || count($this->_headScripts); } + public static function getPradoPackages() + { + return self::$_pradoPackages; + } + + public static function getPradoScripts() + { + return self::$_pradoScripts; + } + /** * Registers Prado javascript by library name. See "Web/Javascripts/source/packages.php" * for library names. @@ -191,6 +203,37 @@ class TClientScriptManager extends TApplicationComponent } } + /** + * Returns the URLs of all script files referenced on the page + * @return array Combined list of all script urls used in the page + */ + public function getScriptUrls() + { + $scripts = array(); + + $packages=array_keys($this->_registeredPradoScripts); + $base = Prado::getFrameworkPath().DIRECTORY_SEPARATOR.self::SCRIPT_PATH; + list($path,$baseUrl)=$this->getPackagePathUrl($base); + foreach ($packages as $p) + { + foreach (self::$_pradoScripts[$p] as $dep) + { + foreach (self::$_pradoPackages[$dep] as $script) + { + if (!in_array($url=$baseUrl.'/'.$script,$scripts)) + $scripts[]=$url; + } + } + } + + $scripts = array_merge($scripts, array_values($this->_headScriptFiles)); + $scripts = array_merge($scripts, array_values($this->_scriptFiles)); + + $scripts = array_unique($scripts); + + return $scripts; + } + /** * Publishes a javascript library path and register packages to be loaded. * See TClientScriptLoader for component that enables users to register custom javascript libraries. @@ -423,6 +466,23 @@ class TClientScriptManager extends TApplicationComponent $this->_page->registerCachingAction('Page.ClientScript','registerStyleSheet',$params); } + /** + * Returns the URLs of all stylesheet files referenced on the page + * @return array Combined list of all stylesheet urls used in the page + */ + public function getStyleSheetUrls() + { + $stylesheets = array_values($this->_styleSheets); + + foreach(Prado::getApplication()->getAssetManager()->getPublished() as $path=>$url) + if (substr($url,strlen($url)-4)=='.css') + $stylesheets[] = $url; + + $stylesheets = array_unique($stylesheets); + + return $stylesheets; + } + /** * Registers a javascript file in the page head * @param string a unique key identifying the file @@ -714,6 +774,11 @@ class TClientScriptManager extends TApplicationComponent $writer->write("
\n".$str."
\n"); } + public function getHiddenFields() + { + return $this->_hiddenFields; + } + /** * Checks whether page rendering has not begun yet */ diff --git a/framework/Web/UI/WebControls/THtmlArea.php b/framework/Web/UI/WebControls/THtmlArea.php index 2411f8f3..383411c5 100644 --- a/framework/Web/UI/WebControls/THtmlArea.php +++ b/framework/Web/UI/WebControls/THtmlArea.php @@ -402,6 +402,8 @@ class THtmlArea extends TTextBox $options['debug'] = false; $js = TJavaScript::encode($options,true,true); $script = "if(typeof(tinyMCE_GZ)!='undefined'){ tinyMCE_GZ.init({$js}); }"; + if ($this->getPage()->getIsCallback()) + $script.= 'tinymce.dom.Event._pageInit();'; $scripts->registerEndScript($key, $script); } } @@ -420,7 +422,7 @@ class THtmlArea extends TTextBox { $scripts = $this->getPage()->getClientScript(); $options = TJavaScript::encode($this->getEditorOptions(),true,true); // Force encoding of empty strings - $script = "if(typeof(tinyMCE)!='undefined'){ tinyMCE.init($options); }"; + $script = "if(typeof(tinyMCE)!='undefined')\r\n{ tinyMCE.init($options); }"; $scripts->registerEndScript('prado:THtmlArea'.$this->ClientID,$script); } -- cgit v1.2.3