diff options
author | emkael <emkael@tlen.pl> | 2016-02-24 23:18:07 +0100 |
---|---|---|
committer | emkael <emkael@tlen.pl> | 2016-02-24 23:18:07 +0100 |
commit | 6f7fdef0f500cd4bb540affd3bc1482243f337c1 (patch) | |
tree | 4853eecd0769a903e6130c1896e1d070848150dd /lib/prado/framework/Web/UI/TClientScriptManager.php | |
parent | 61f2ea48a4e11cb5fb941b3783e19c9e9ef38a45 (diff) |
* Prado 3.3.0
Diffstat (limited to 'lib/prado/framework/Web/UI/TClientScriptManager.php')
-rw-r--r-- | lib/prado/framework/Web/UI/TClientScriptManager.php | 849 |
1 files changed, 849 insertions, 0 deletions
diff --git a/lib/prado/framework/Web/UI/TClientScriptManager.php b/lib/prado/framework/Web/UI/TClientScriptManager.php new file mode 100644 index 0000000..e35a1ca --- /dev/null +++ b/lib/prado/framework/Web/UI/TClientScriptManager.php @@ -0,0 +1,849 @@ +<?php +/** + * TClientScriptManager and TClientSideOptions class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Gabor Berczi <gabor.berczi@devworx.hu> (lazyload additions & progressive rendering) + * @link https://github.com/pradosoft/prado + * @copyright Copyright © 2005-2015 The PRADO Group + * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT + * @package System.Web.UI + */ + +/** + * TClientScriptManager class. + * + * TClientScriptManager manages javascript and CSS stylesheets for a page. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Gabor Berczi <gabor.berczi@devworx.hu> (lazyload additions & progressive rendering) + * @package System.Web.UI + * @since 3.0 + */ +class TClientScriptManager extends TApplicationComponent +{ + /** + * directory containing Prado javascript files + */ + const SCRIPT_PATH='Web/Javascripts/source'; + /** + * file containing javascript packages and their cross dependencies + */ + const PACKAGES_FILE='Web/Javascripts/packages.php'; + /** + * @var TPage page who owns this manager + */ + private $_page; + /** + * @var array registered hidden fields, indexed by hidden field names + */ + private $_hiddenFields=array(); + /** + * @var array javascript blocks to be rendered at the beginning of the form + */ + private $_beginScripts=array(); + /** + * @var array javascript blocks to be rendered at the end of the form + */ + private $_endScripts=array(); + /** + * @var array javascript files to be rendered in the form + */ + private $_scriptFiles=array(); + /** + * @var array javascript files to be rendered in page head section + */ + private $_headScriptFiles=array(); + /** + * @var array javascript blocks to be rendered in page head section + */ + private $_headScripts=array(); + /** + * @var array CSS files + */ + private $_styleSheetFiles=array(); + /** + * @var array CSS declarations + */ + private $_styleSheets=array(); + /** + * @var array registered PRADO script libraries + */ + private $_registeredPradoScripts=array(); + /** + * Client-side javascript library dependencies, loads from PACKAGES_FILE; + * @var array + */ + private static $_pradoScripts; + /** + * Client-side javascript library packages, loads from PACKAGES_FILE; + * @var array + */ + private static $_pradoPackages; + + private $_renderedHiddenFields; + + private $_renderedScriptFiles=array(); + + private $_expandedPradoScripts; + + /** + * Constructor. + * @param TPage page that owns this client script manager + */ + public function __construct(TPage $owner) + { + $this->_page=$owner; + } + + /** + * @return boolean whether THead is required in order to render CSS and js within head + * @since 3.1.1 + */ + public function getRequiresHead() + { + return count($this->_styleSheetFiles) || count($this->_styleSheets) + || 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/packages.php" + * for library names. + * @param string script library name. + */ + public function registerPradoScript($name) + { + $this->registerPradoScriptInternal($name); + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerPradoScript',$params); + } + + /** + * Registers a Prado javascript library to be loaded. + */ + protected function registerPradoScriptInternal($name) + { + // $this->checkIfNotInRender(); + if(!isset($this->_registeredPradoScripts[$name])) + { + if(self::$_pradoScripts === null) + { + $packageFile = Prado::getFrameworkPath().DIRECTORY_SEPARATOR.self::PACKAGES_FILE; + list($packages,$deps)= include($packageFile); + self::$_pradoScripts = $deps; + self::$_pradoPackages = $packages; + } + + if (isset(self::$_pradoScripts[$name])) + $this->_registeredPradoScripts[$name]=true; + else + throw new TInvalidOperationException('csmanager_pradoscript_invalid',$name); + + if(($packages=array_keys($this->_registeredPradoScripts))!==array()) + { + $base = Prado::getFrameworkPath().DIRECTORY_SEPARATOR.self::SCRIPT_PATH; + list($path,$baseUrl)=$this->getPackagePathUrl($base); + $packagesUrl=array(); + $isDebug=$this->getApplication()->getMode()===TApplicationMode::Debug; + foreach ($packages as $p) + { + foreach (self::$_pradoScripts[$p] as $dep) + { + foreach (self::$_pradoPackages[$dep] as $script) + if (!isset($this->_expandedPradoScripts[$script])) + { + $this->_expandedPradoScripts[$script] = true; + if($isDebug) + { + if (!in_array($url=$baseUrl.'/'.$script,$packagesUrl)) + $packagesUrl[]=$url; + } else { + if (!in_array($url=$baseUrl.'/min/'.$script,$packagesUrl)) + { + if(!is_file($filePath=$path.'/min/'.$script)) + { + $dirPath=dirname($filePath); + if(!is_dir($dirPath)) + mkdir($dirPath, PRADO_CHMOD, true); + file_put_contents($filePath, TJavaScript::JSMin(file_get_contents($base.'/'.$script))); + chmod($filePath, PRADO_CHMOD); + } + $packagesUrl[]=$url; + } + } + } + } + } + foreach($packagesUrl as $url) + $this->registerScriptFile($url,$url); + } + } + } + + /** + * @return string Prado javascript library base asset url. + */ + public function getPradoScriptAssetUrl() + { + $base = Prado::getFrameworkPath().DIRECTORY_SEPARATOR.self::SCRIPT_PATH; + $assets = Prado::getApplication()->getAssetManager(); + return $assets->getPublishedUrl($base); + } + + /** + * 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_values($this->_headScriptFiles); + $scripts = array_merge($scripts, array_values($this->_scriptFiles)); + $scripts = array_unique($scripts); + + return $scripts; + } + + /** + * @param string javascript package path. + * @return array tuple($path,$url). + */ + protected function getPackagePathUrl($base) + { + $assets = Prado::getApplication()->getAssetManager(); + if(strpos($base, $assets->getBaseUrl())===false) + { + if(($dir = Prado::getPathOfNameSpace($base)) !== null) { + $base = $dir; + } + return array($assets->getPublishedPath($base), $assets->publishFilePath($base)); + } + else + { + return array($assets->getBasePath().str_replace($assets->getBaseUrl(),'',$base), $base); + } + } + + /** + * Returns javascript statement that create a new callback request object. + * @param ICallbackEventHandler callback response handler + * @param array additional callback options + * @return string javascript statement that creates a new callback request. + */ + public function getCallbackReference(ICallbackEventHandler $callbackHandler, $options=null) + { + $options = !is_array($options) ? array() : $options; + $class = new ReflectionClass($callbackHandler); + $clientSide = $callbackHandler->getActiveControl()->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 callback javascript for a control. + * @param string javascript class responsible for the control being registered for callback + * @param array callback options + */ + public function registerCallbackControl($class, $options) + { + $optionString=TJavaScript::encode($options); + $code="new {$class}({$optionString});"; + $this->_endScripts[sprintf('%08X', crc32($code))]=$code; + $this->registerPradoScriptInternal('ajax'); + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerCallbackControl',$params); + } + + /** + * Registers postback javascript for a control. A null class parameter will prevent + * the javascript code registration. + * @param string javascript class responsible for the control being registered for postback + * @param array postback options + */ + public function registerPostBackControl($class,$options) + { + if($class === null) { + return; + } + if(!isset($options['FormID']) && ($form=$this->_page->getForm())!==null) + $options['FormID']=$form->getClientID(); + $optionString=TJavaScript::encode($options); + $code="new {$class}({$optionString});"; + + $this->_endScripts[sprintf('%08X', crc32($code))]=$code; + $this->registerPradoScriptInternal('prado'); + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerPostBackControl',$params); + } + + /** + * Register a default button to panel. When the $panel is in focus and + * the 'enter' key is pressed, the $button will be clicked. + * @param TControl|string panel (or its unique ID) to register the default button action + * @param TControl|string button (or its unique ID) to trigger a postback + */ + public function registerDefaultButton($panel, $button) + { + $panelID=is_string($panel)?$panel:$panel->getUniqueID(); + + if(is_string($button)) + $buttonID=$button; + else + { + $button->setIsDefaultButton(true); + $buttonID=$button->getUniqueID(); + } + $options = TJavaScript::encode($this->getDefaultButtonOptions($panelID, $buttonID)); + $code = "new Prado.WebUI.DefaultButton($options);"; + + $this->_endScripts['prado:'.$panelID]=$code; + $this->registerPradoScriptInternal('prado'); + + $params=array($panelID,$buttonID); + $this->_page->registerCachingAction('Page.ClientScript','registerDefaultButton',$params); + } + + /** + * @param string the unique ID of the container control + * @param string the unique ID of the button control + * @return array default button options. + */ + protected function getDefaultButtonOptions($panelID, $buttonID) + { + $options['ID'] = TControl::convertUniqueIdToClientId($panelID); + $options['Panel'] = TControl::convertUniqueIdToClientId($panelID); + $options['Target'] = TControl::convertUniqueIdToClientId($buttonID); + $options['EventTarget'] = $buttonID; + $options['Event'] = 'click'; + return $options; + } + + /** + * Registers the control to receive default focus. + * @param string the client ID of the control to receive default focus + */ + public function registerFocusControl($target) + { + $this->registerPradoScriptInternal('jquery'); + if($target instanceof TControl) + $target=$target->getClientID(); + $this->_endScripts['prado:focus'] = 'jQuery(\'#'.$target.'\').focus();'; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerFocusControl',$params); + } + + /** + * Registers a CSS file to be rendered in the page head + * + * The CSS files in themes are registered in {@link OnPreRenderComplete onPreRenderComplete} if you want to override + * CSS styles in themes you need to register it after this event is completed. + * + * Example: + * <code> + * <?php + * class BasePage extends TPage { + * public function onPreRenderComplete($param) { + * parent::onPreRenderComplete($param); + * $url = 'path/to/your/stylesheet.css'; + * $this->Page->ClientScript->registerStyleSheetFile($url, $url); + * } + * } + * </code> + * + * @param string a unique key identifying the file + * @param string URL to the CSS file + * @param string media type of the CSS (such as 'print', 'screen', etc.). Defaults to empty, meaning the CSS applies to all media types. + */ + public function registerStyleSheetFile($key,$url,$media='') + { + if($media==='') + $this->_styleSheetFiles[$key]=$url; + else + $this->_styleSheetFiles[$key]=array($url,$media); + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerStyleSheetFile',$params); + } + + /** + * Registers a CSS block to be rendered in the page head + * @param string a unique key identifying the CSS block + * @param string CSS block + */ + public function registerStyleSheet($key,$css,$media='') + { + $this->_styleSheets[$key]=$css; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerStyleSheet',$params); + } + + /** + * Returns the URLs of all stylesheet files referenced on the page + * @return array List of all stylesheet urls used in the page + */ + public function getStyleSheetUrls() + { + $stylesheets = array_values( + array_map( + create_function('$e', 'return is_array($e) ? $e[0] : $e;'), + $this->_styleSheetFiles) + ); + + foreach(Prado::getApplication()->getAssetManager()->getPublished() as $path=>$url) + if (substr($url,strlen($url)-4)=='.css') + $stylesheets[] = $url; + + $stylesheets = array_unique($stylesheets); + + return $stylesheets; + } + + /** + * Returns all the stylesheet code snippets referenced on the page + * @return array List of all stylesheet snippets used in the page + */ + public function getStyleSheetCodes() + { + return array_unique(array_values($this->_styleSheets)); + } + + /** + * Registers a javascript file in the page head + * @param string a unique key identifying the file + * @param string URL to the javascript file + */ + public function registerHeadScriptFile($key,$url) + { + $this->checkIfNotInRender(); + $this->_headScriptFiles[$key]=$url; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerHeadScriptFile',$params); + } + + /** + * Registers a javascript block in the page head. + * @param string a unique key identifying the script block + * @param string javascript block + */ + public function registerHeadScript($key,$script) + { + $this->checkIfNotInRender(); + $this->_headScripts[$key]=$script; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerHeadScript',$params); + } + + /** + * Registers a javascript file to be rendered within the form + * @param string a unique key identifying the file + * @param string URL to the javascript file to be rendered + */ + public function registerScriptFile($key, $url) + { + $this->_scriptFiles[$key]=$url; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerScriptFile',$params); + } + + /** + * Registers a javascript script block at the beginning of the form + * @param string a unique key identifying the script block + * @param string javascript block + */ + public function registerBeginScript($key,$script) + { + $this->checkIfNotInRender(); + $this->_beginScripts[$key]=$script; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerBeginScript',$params); + } + + /** + * Registers a javascript script block at the end of the form + * @param string a unique key identifying the script block + * @param string javascript block + */ + public function registerEndScript($key,$script) + { + $this->_endScripts[$key]=$script; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerEndScript',$params); + } + + /** + * Registers a hidden field to be rendered in the form. + * @param string a unique key identifying the hidden field + * @param string|array hidden field value, if the value is an array, every element + * in the array will be rendered as a hidden field value. + */ + public function registerHiddenField($name,$value) + { + $this->_hiddenFields[$name]=$value; + + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','registerHiddenField',$params); + } + + /** + * @param string a unique key + * @return boolean whether there is a CSS file registered with the specified key + */ + public function isStyleSheetFileRegistered($key) + { + return isset($this->_styleSheetFiles[$key]); + } + + /** + * @param string a unique key + * @return boolean whether there is a CSS block registered with the specified key + */ + public function isStyleSheetRegistered($key) + { + return isset($this->_styleSheets[$key]); + } + + /** + * @param string a unique key + * @return boolean whether there is a head javascript file registered with the specified key + */ + public function isHeadScriptFileRegistered($key) + { + return isset($this->_headScriptFiles[$key]); + } + + /** + * @param string a unique key + * @return boolean whether there is a head javascript block registered with the specified key + */ + public function isHeadScriptRegistered($key) + { + return isset($this->_headScripts[$key]); + } + + /** + * @param string a unique key + * @return boolean whether there is a javascript file registered with the specified key + */ + public function isScriptFileRegistered($key) + { + return isset($this->_scriptFiles[$key]); + } + + /** + * @param string a unique key + * @return boolean whether there is a beginning javascript block registered with the specified key + */ + public function isBeginScriptRegistered($key) + { + return isset($this->_beginScripts[$key]); + } + + /** + * @param string a unique key + * @return boolean whether there is an ending javascript block registered with the specified key + */ + public function isEndScriptRegistered($key) + { + return isset($this->_endScripts[$key]); + } + + /** + * @return boolean true if any end scripts are registered. + */ + public function hasEndScripts() + { + return count($this->_endScripts) > 0; + } + + /** + * @return boolean true if any begin scripts are registered. + */ + public function hasBeginScripts() + { + return count($this->_beginScripts) > 0; + } + + /** + * @param string a unique key + * @return boolean whether there is a hidden field registered with the specified key + */ + public function isHiddenFieldRegistered($key) + { + return isset($this->_hiddenFields[$key]); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderStyleSheetFiles($writer) + { + $str=''; + foreach($this->_styleSheetFiles as $url) + { + if(is_array($url)) + $str.="<link rel=\"stylesheet\" type=\"text/css\" media=\"{$url[1]}\" href=\"".THttpUtility::htmlEncode($url[0])."\" />\n"; + else + $str.="<link rel=\"stylesheet\" type=\"text/css\" href=\"".THttpUtility::htmlEncode($url)."\" />\n"; + } + $writer->write($str); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderStyleSheets($writer) + { + if(count($this->_styleSheets)) + $writer->write("<style type=\"text/css\">\n/*<![CDATA[*/\n".implode("\n",$this->_styleSheets)."\n/*]]>*/\n</style>\n"); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderHeadScriptFiles($writer) + { + $this->renderScriptFiles($writer,$this->_headScriptFiles); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderHeadScripts($writer) + { + $writer->write(TJavaScript::renderScriptBlocks($this->_headScripts)); + } + + public function renderScriptFilesBegin($writer) + { + $this->renderAllPendingScriptFiles($writer); + } + + public function renderScriptFilesEnd($writer) + { + $this->renderAllPendingScriptFiles($writer); + } + + public function markScriptFileAsRendered($url) + { + $this->_renderedScriptFiles[$url] = $url; + $params=func_get_args(); + $this->_page->registerCachingAction('Page.ClientScript','markScriptFileAsRendered',$params); + } + + protected function renderScriptFiles($writer, Array $scripts) + { + foreach($scripts as $script) + { + $writer->write(TJavaScript::renderScriptFile($script)); + $this->markScriptFileAsRendered($script); + } + } + + protected function getRenderedScriptFiles() + { + return $this->_renderedScriptFiles; + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderAllPendingScriptFiles($writer) + { + if(!empty($this->_scriptFiles)) + { + $addedScripts = array_diff($this->_scriptFiles,$this->getRenderedScriptFiles()); + $this->renderScriptFiles($writer,$addedScripts); + } + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderBeginScripts($writer) + { + $writer->write(TJavaScript::renderScriptBlocks($this->_beginScripts)); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderEndScripts($writer) + { + $writer->write(TJavaScript::renderScriptBlocks($this->_endScripts)); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderBeginScriptsCallback($writer) + { + $writer->write(TJavaScript::renderScriptBlocksCallback($this->_beginScripts)); + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + public function renderEndScriptsCallback($writer) + { + $writer->write(TJavaScript::renderScriptBlocksCallback($this->_endScripts)); + } + + public function renderHiddenFieldsBegin($writer) + { + $this->renderHiddenFieldsInt($writer,true); + } + + public function renderHiddenFieldsEnd($writer) + { + $this->renderHiddenFieldsInt($writer,false); + } + + /** + * Flushes all pending script registrations + * @param THtmlWriter writer for the rendering purpose + * @param TControl the control forcing the flush (used only in error messages) + */ + public function flushScriptFiles($writer, $control=null) + { + if(!$this->_page->getIsCallback()) + { + $this->_page->ensureRenderInForm($control); + $this->renderAllPendingScriptFiles($writer); + } + } + + /** + * @param THtmlWriter writer for the rendering purpose + */ + 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)) + { + foreach($value as $v) + $str.='<input type="hidden" name="'.$name.'[]" id="'.$id.'" value="'.THttpUtility::htmlEncode($value)."\" />\n"; + } + else + { + $str.='<input type="hidden" name="'.$name.'" id="'.$id.'" value="'.THttpUtility::htmlEncode($value)."\" />\n"; + } + $this->_renderedHiddenFields[] = $name; + } + if($str!=='') + $writer->write("<div style=\"visibility:hidden;\">\n".$str."</div>\n"); + } + + public function getHiddenFields() + { + return $this->_hiddenFields; + } + + /** + * 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'); + } +} + +/** + * TClientSideOptions abstract class. + * + * TClientSideOptions manages client-side options for components that have + * common client-side javascript behaviours and client-side events such as + * between ActiveControls and validators. + * + * @author <weizhuo[at]gmail[dot]com> + * @package System.Web.UI + * @since 3.0 + */ +abstract class TClientSideOptions extends TComponent +{ + /** + * @var TMap list of client-side options. + */ + private $_options; + + /** + * Adds on client-side event handler by wrapping the code within a + * javascript function block. If the code begins with "javascript:", the + * code is assumed to be a javascript function block rather than arbiturary + * javascript statements. + * @param string option name + * @param string javascript statements. + */ + protected function setFunction($name, $code) + { + if(!TJavaScript::isJsLiteral($code)) + $code = TJavaScript::quoteJsLiteral($this->ensureFunction($code)); + $this->setOption($name, $code); + } + + /** + * @return string gets a particular option, null if not set. + */ + protected function getOption($name) + { + if ($this->_options) + return $this->_options->itemAt($name); + else + return null; + } + + /** + * @param string option name + * @param mixed option value. + */ + protected function setOption($name, $value) + { + $this->getOptions()->add($name, $value); + } + + /** + * @return TMap gets the list of options as TMap + */ + public function getOptions() + { + if (!$this->_options) + $this->_options = Prado::createComponent('System.Collections.TMap'); + return $this->_options; + } + + /** + * Ensure that the javascript statements are wrapped in a javascript + * function block as <code>function(sender, parameter){ //code }</code>. + */ + protected function ensureFunction($javascript) + { + return "function(sender, parameter){ {$javascript} }"; + } +} + |