Commit 41ea9485 by Qiang Xue

refactoring and documentation for asset/script management.

parent 4e8f9cea
......@@ -10,6 +10,7 @@ namespace yii\base;
use Yii;
use yii\base\Application;
use yii\helpers\FileHelper;
use yii\helpers\Html;
/**
* View represents a view object in the MVC pattern.
......@@ -22,22 +23,48 @@ use yii\helpers\FileHelper;
class View extends Component
{
/**
* @event Event an event that is triggered by [[renderFile()]] right before it renders a view file.
* @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file.
*/
const EVENT_BEFORE_RENDER = 'beforeRender';
/**
* @event Event an event that is triggered by [[renderFile()]] right after it renders a view file.
* @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file.
*/
const EVENT_AFTER_RENDER = 'afterRender';
/**
* @var object the object that owns this view. This can be a controller, a widget, or any other object.
* The location of registered JavaScript code block or files.
* This means the location is in the head section.
*/
public $context;
const POS_HEAD = 1;
/**
* The location of registered JavaScript code block or files.
* This means the location is at the beginning of the body section.
*/
const POS_BEGIN = 2;
/**
* The location of registered JavaScript code block or files.
* This means the location is at the end of the body section.
*/
const POS_END = 3;
/**
* This is internally used as the placeholder for receiving the content registered for the head section.
*/
const PL_HEAD = '<![CDATA[YII-BLOCK-HEAD]]>';
/**
* This is internally used as the placeholder for receiving the content registered for the beginning of the body section.
*/
const PL_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN]]>';
/**
* @var ViewContent
* This is internally used as the placeholder for receiving the content registered for the end of the body section.
*/
public $page;
const PL_BODY_END = '<![CDATA[YII-BLOCK-BODY-END]]>';
/**
* @var object the context under which the [[renderFile()]] method is being invoked.
* This can be a controller, a widget, or any other object.
*/
public $context;
/**
* @var mixed custom parameters that are shared among view templates.
*/
......@@ -48,32 +75,75 @@ class View extends Component
*/
public $renderer;
/**
* @var Theme|array the theme object or the configuration array for creating the theme.
* @var Theme|array the theme object or the configuration array for creating the theme object.
* If not set, it means theming is not enabled.
*/
public $theme;
/**
* @var array a list of named output blocks. The keys are the block names and the values
* are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]]
* to capture small fragments of a view. They can be later accessed at somewhere else
* to capture small fragments of a view. They can be later accessed somewhere else
* through this property.
*/
public $blocks;
/**
* @var Widget[] the widgets that are currently being rendered (not ended). This property
* is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it.
* is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly.
* @internal
*/
public $widgetStack = array();
/**
* @var array a list of currently active fragment cache widgets. This property
* is used internally to implement the content caching feature. Do not modify it.
* is used internally to implement the content caching feature. Do not modify it directly.
* @internal
*/
public $cacheStack = array();
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it.
* is used internally to implement the content caching feature. Do not modify it directly.
* @internal
*/
public $dynamicPlaceholders = array();
/**
* @var array the registered asset bundles. The keys are the bundle names, and the values
* are the corresponding [[AssetBundle]] objects.
* @see registerAssetBundle
*/
public $assetBundles;
/**
* @var string the page title
*/
public $title;
/**
* @var array the registered meta tags.
* @see registerMetaTag
*/
public $metaTags;
/**
* @var array the registered link tags.
* @see registerLinkTag
*/
public $linkTags;
/**
* @var array the registered CSS code blocks.
* @see registerCss
*/
public $css;
/**
* @var array the registered CSS files.
* @see registerCssFile
*/
public $cssFiles;
/**
* @var array the registered JS code blocks
* @see registerJs
*/
public $js;
/**
* @var array the registered JS files.
* @see registerJsFile
*/
public $jsFiles;
/**
......@@ -88,11 +158,6 @@ class View extends Component
if (is_array($this->theme)) {
$this->theme = Yii::createObject($this->theme);
}
if (is_array($this->page)) {
$this->page = Yii::createObject($this->page);
} else {
$this->page = new ViewContent;
}
}
/**
......@@ -445,4 +510,273 @@ class View extends Component
{
$this->endWidget();
}
private $_assetManager;
/**
* Registers the asset manager being used by this view object.
* @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component.
*/
public function getAssetManager()
{
return $this->_assetManager ?: Yii::$app->getAssetManager();
}
/**
* Sets the asset manager.
* @param \yii\web\AssetManager $value the asset manager
*/
public function setAssetManager($value)
{
$this->_assetManager = $value;
}
/**
* Marks the beginning of an HTML page.
*/
public function beginPage()
{
ob_start();
ob_implicit_flush(false);
}
/**
* Marks the ending of an HTML page.
*/
public function endPage()
{
$content = ob_get_clean();
echo strtr($content, array(
self::PL_HEAD => $this->renderHeadHtml(),
self::PL_BODY_BEGIN => $this->renderBodyBeginHtml(),
self::PL_BODY_END => $this->renderBodyEndHtml(),
));
unset(
$this->assetBundles,
$this->metaTags,
$this->linkTags,
$this->css,
$this->cssFiles,
$this->js,
$this->jsFiles
);
}
/**
* Marks the beginning of an HTML body section.
*/
public function beginBody()
{
echo self::PL_BODY_BEGIN;
}
/**
* Marks the ending of an HTML body section.
*/
public function endBody()
{
echo self::PL_BODY_END;
}
/**
* Marks the position of an HTML head section.
*/
public function head()
{
echo self::PL_HEAD;
}
/**
* Registers the named asset bundle.
* All dependent asset bundles will be registered.
* @param string $name the name of the asset bundle.
* @throws InvalidConfigException if the asset bundle does not exist or a cyclic dependency is detected
*/
public function registerAssetBundle($name)
{
if (!isset($this->assetBundles[$name])) {
$am = $this->getAssetManager();
$bundle = $am->getBundle($name);
if ($bundle !== null) {
$this->assetBundles[$name] = false;
$bundle->registerAssets($this);
$this->assetBundles[$name] = true;
} else {
throw new InvalidConfigException("Unknown asset bundle: $name");
}
} elseif ($this->assetBundles[$name] === false) {
throw new InvalidConfigException("A cyclic dependency is detected for bundle '$name'.");
}
}
/**
* Registers a meta tag.
* @param array $options the HTML attributes for the meta tag.
* @param string $key the key that identifies the meta tag. If two meta tags are registered
* with the same key, the latter will overwrite the former. If this is null, the new meta tag
* will be appended to the existing ones.
*/
public function registerMetaTag($options, $key = null)
{
if ($key === null) {
$this->metaTags[] = Html::tag('meta', '', $options);
} else {
$this->metaTags[$key] = Html::tag('meta', '', $options);
}
}
/**
* Registers a link tag.
* @param array $options the HTML attributes for the link tag.
* @param string $key the key that identifies the link tag. If two link tags are registered
* with the same key, the latter will overwrite the former. If this is null, the new link tag
* will be appended to the existing ones.
*/
public function registerLinkTag($options, $key = null)
{
if ($key === null) {
$this->linkTags[] = Html::tag('link', '', $options);
} else {
$this->linkTags[$key] = Html::tag('link', '', $options);
}
}
/**
* Registers a CSS code block.
* @param string $css the CSS code block to be registered
* @param array $options the HTML attributes for the style tag.
* @param string $key the key that identifies the CSS code block. If null, it will use
* $css as the key. If two CSS code blocks are registered with the same key, the latter
* will overwrite the former.
*/
public function registerCss($css, $options = array(), $key = null)
{
$key = $key ?: $css;
$this->css[$key] = Html::style($css, $options);
}
/**
* Registers a CSS file.
* @param string $url the CSS file to be registered.
* @param array $options the HTML attributes for the link tag.
* @param string $key the key that identifies the CSS script file. If null, it will use
* $url as the key. If two CSS files are registered with the same key, the latter
* will overwrite the former.
*/
public function registerCssFile($url, $options = array(), $key = null)
{
$key = $key ?: $url;
$this->cssFiles[$key] = Html::cssFile($url, $options);
}
/**
* Registers a JS code block.
* @param string $js the JS code block to be registered
* @param array $options the HTML attributes for the script tag. A special option
* named "position" is supported which specifies where the JS script tag should be inserted
* in a page. The possible values of "position" are:
*
* - [[POS_HEAD]]: in the head section
* - [[POS_BEGIN]]: at the beginning of the body section
* - [[POS_END]]: at the end of the body section
*
* @param string $key the key that identifies the JS code block. If null, it will use
* $js as the key. If two JS code blocks are registered with the same key, the latter
* will overwrite the former.
*/
public function registerJs($js, $options = array(), $key = null)
{
$position = isset($options['position']) ? $options['position'] : self::POS_END;
unset($options['position']);
$key = $key ?: $js;
$this->js[$position][$key] = Html::script($js, $options);
}
/**
* Registers a JS file.
* @param string $url the JS file to be registered.
* @param array $options the HTML attributes for the script tag. A special option
* named "position" is supported which specifies where the JS script tag should be inserted
* in a page. The possible values of "position" are:
*
* - [[POS_HEAD]]: in the head section
* - [[POS_BEGIN]]: at the beginning of the body section
* - [[POS_END]]: at the end of the body section
*
* @param string $key the key that identifies the JS script file. If null, it will use
* $url as the key. If two JS files are registered with the same key, the latter
* will overwrite the former.
*/
public function registerJsFile($url, $options = array(), $key = null)
{
$position = isset($options['position']) ? $options['position'] : self::POS_END;
unset($options['position']);
$key = $key ?: $url;
$this->jsFiles[$position][$key] = Html::jsFile($url, $options);
}
/**
* Renders the content to be inserted in the head section.
* The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
* @return string the rendered content
*/
protected function renderHeadHtml()
{
$lines = array();
if (!empty($this->metaTags)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->linkTags)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->cssFiles)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->css)) {
$lines[] = implode("\n", $this->css);
}
if (!empty($this->jsFiles[self::POS_HEAD])) {
$lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
}
if (!empty($this->js[self::POS_HEAD])) {
$lines[] = implode("\n", $this->js[self::POS_HEAD]);
}
return implode("\n", $lines);
}
/**
* Renders the content to be inserted at the beginning of the body section.
* The content is rendered using the registered JS code blocks and files.
* @return string the rendered content
*/
protected function renderBodyBeginHtml()
{
$lines = array();
if (!empty($this->jsFiles[self::POS_BEGIN])) {
$lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
}
if (!empty($this->js[self::POS_BEGIN])) {
$lines[] = implode("\n", $this->js[self::POS_BEGIN]);
}
return implode("\n", $lines);
}
/**
* Renders the content to be inserted at the end of the body section.
* The content is rendered using the registered JS code blocks and files.
* @return string the rendered content
*/
protected function renderBodyEndHtml()
{
$lines = array();
if (!empty($this->jsFiles[self::POS_END])) {
$lines[] = implode("\n", $this->jsFiles[self::POS_END]);
}
if (!empty($this->js[self::POS_END])) {
$lines[] = implode("\n", $this->js[self::POS_END]);
}
return implode("\n", $lines);
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use yii\helpers\Html;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ViewContent extends Component
{
const POS_HEAD = 1;
const POS_BEGIN = 2;
const POS_END = 3;
const TOKEN_HEAD = '<![CDATA[YII-BLOCK-HEAD]]>';
const TOKEN_BODY_BEGIN = '<![CDATA[YII-BLOCK-BODY-BEGIN]]>';
const TOKEN_BODY_END = '<![CDATA[YII-BLOCK-BODY-END]]>';
public $assetBundles;
public $title;
public $metaTags;
public $linkTags;
public $css;
public $cssFiles;
public $js;
public $jsFiles;
public $jsInHead;
public $jsFilesInHead;
public $jsInBody;
public $jsFilesInBody;
public function reset()
{
$this->assetBundles = null;
$this->title = null;
$this->metaTags = null;
$this->linkTags = null;
$this->css = null;
$this->cssFiles = null;
$this->js = null;
$this->jsFiles = null;
$this->jsInHead = null;
$this->jsFilesInHead = null;
$this->jsInBody = null;
$this->jsFilesInBody = null;
}
private $_assetManager;
/**
* @return \yii\web\AssetManager
*/
public function getAssetManager()
{
return $this->_assetManager ?: Yii::$app->getAssets();
}
public function setAssetManager($value)
{
$this->_assetManager = $value;
}
public function begin()
{
ob_start();
ob_implicit_flush(false);
}
public function end()
{
$content = ob_get_clean();
echo $this->populate($content);
}
public function beginBody()
{
echo self::TOKEN_BODY_BEGIN;
}
public function endBody()
{
echo self::TOKEN_BODY_END;
}
public function head()
{
echo self::TOKEN_HEAD;
}
public function registerAssetBundle($name)
{
if (!isset($this->assetBundles[$name])) {
$am = $this->getAssetManager();
$bundle = $am->getBundle($name);
if ($bundle !== null) {
$this->assetBundles[$name] = false;
$bundle->registerAssets($this, $am);
$this->assetBundles[$name] = true;
} else {
throw new InvalidConfigException("Unknown asset bundle: $name");
}
} elseif ($this->assetBundles[$name] === false) {
throw new InvalidConfigException("A cyclic dependency is detected for bundle '$name'.");
}
}
public function registerMetaTag($options, $key = null)
{
if ($key === null) {
$this->metaTags[] = Html::tag('meta', '', $options);
} else {
$this->metaTags[$key] = Html::tag('meta', '', $options);
}
}
public function registerLinkTag($options, $key = null)
{
if ($key === null) {
$this->linkTags[] = Html::tag('link', '', $options);
} else {
$this->linkTags[$key] = Html::tag('link', '', $options);
}
}
public function registerCss($css, $options = array(), $key = null)
{
$key = $key ?: $css;
$this->css[$key] = Html::style($css, $options);
}
public function registerCssFile($url, $options = array(), $key = null)
{
$key = $key ?: $url;
$this->cssFiles[$key] = Html::cssFile($url, $options);
}
public function registerJs($js, $options = array(), $key = null)
{
$position = isset($options['position']) ? $options['position'] : self::POS_END;
unset($options['position']);
$key = $key ?: $js;
$html = Html::script($js, $options);
if ($position == self::POS_END) {
$this->js[$key] = $html;
} elseif ($position == self::POS_HEAD) {
$this->jsInHead[$key] = $html;
} elseif ($position == self::POS_BEGIN) {
$this->jsInBody[$key] = $html;
} else {
throw new InvalidParamException("Unknown position: $position");
}
}
public function registerJsFile($url, $options = array(), $key = null)
{
$position = isset($options['position']) ? $options['position'] : self::POS_END;
unset($options['position']);
$key = $key ?: $url;
$html = Html::jsFile($url, $options);
if ($position == self::POS_END) {
$this->jsFiles[$key] = $html;
} elseif ($position == self::POS_HEAD) {
$this->jsFilesInHead[$key] = $html;
} elseif ($position == self::POS_BEGIN) {
$this->jsFilesInBody[$key] = $html;
} else {
throw new InvalidParamException("Unknown position: $position");
}
}
protected function populate($content)
{
return strtr($content, array(
self::TOKEN_HEAD => $this->getHeadHtml(),
self::TOKEN_BODY_BEGIN => $this->getBodyBeginHtml(),
self::TOKEN_BODY_END => $this->getBodyEndHtml(),
));
}
protected function getHeadHtml()
{
$lines = array();
if (!empty($this->metaTags)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->linkTags)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->cssFiles)) {
$lines[] = implode("\n", $this->cssFiles);
}
if (!empty($this->css)) {
$lines[] = implode("\n", $this->css);
}
if (!empty($this->jsFilesInHead)) {
$lines[] = implode("\n", $this->jsFilesInHead);
}
if (!empty($this->jsInHead)) {
$lines[] = implode("\n", $this->jsInHead);
}
return implode("\n", $lines);
}
protected function getBodyBeginHtml()
{
$lines = array();
if (!empty($this->jsFilesInBody)) {
$lines[] = implode("\n", $this->jsFilesInBody);
}
if (!empty($this->jsInHead)) {
$lines[] = implode("\n", $this->jsInBody);
}
return implode("\n", $lines);
}
protected function getBodyEndHtml()
{
$lines = array();
if (!empty($this->jsFiles)) {
$lines[] = implode("\n", $this->jsFiles);
}
if (!empty($this->js)) {
$lines[] = implode("\n", $this->js);
}
return implode("\n", $lines);
}
}
\ No newline at end of file
......@@ -101,9 +101,9 @@ class Application extends \yii\base\Application
* Returns the asset manager.
* @return AssetManager the asset manager component
*/
public function getAssets()
public function getAssetManager()
{
return $this->getComponent('assets');
return $this->getComponent('assetManager');
}
/**
......@@ -126,7 +126,7 @@ class Application extends \yii\base\Application
'user' => array(
'class' => 'yii\web\User',
),
'assets' => array(
'assetManager' => array(
'class' => 'yii\web\AssetManager',
),
));
......
......@@ -12,6 +12,14 @@ use yii\base\InvalidConfigException;
use yii\base\Object;
/**
* AssetBundle represents a collection of asset files, such as CSS, JS, images.
*
* Each asset bundle has a unique name that globally identifies it among all asset bundles
* used in an application.
*
* An asset bundle can depend on other asset bundles. When registering an asset bundle
* with a view, all its dependent asset bundles will be automatically registered.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
......@@ -66,7 +74,7 @@ class AssetBundle extends Object
*
* Each JavaScript file may be associated with options. In this case, the array key
* should be the JavaScript file path, while the corresponding array value should
* be the option array. The options will be passed to [[ViewContent::registerJsFile()]].
* be the option array. The options will be passed to [[View::registerJsFile()]].
*/
public $js = array();
/**
......@@ -78,7 +86,7 @@ class AssetBundle extends Object
*
* Each CSS file may be associated with options. In this case, the array key
* should be the CSS file path, while the corresponding array value should
* be the option array. The options will be passed to [[ViewContent::registerCssFile()]].
* be the option array. The options will be passed to [[View::registerCssFile()]].
*/
public $css = array();
/**
......@@ -108,14 +116,20 @@ class AssetBundle extends Object
}
/**
* @param \yii\base\ViewContent $page
* @param AssetManager $am
* @throws InvalidConfigException
* Registers the CSS and JS files with the given view.
* This method will first register all dependent asset bundles.
* It will then try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding
* CSS or JS files using [[AssetManager::converter|asset converter]].
* @param \yii\base\View $view the view that the asset files to be registered with.
* @throws InvalidConfigException if [[baseUrl]] or [[basePath]] is not set when the bundle
* contains internal CSS or JS files.
*/
public function registerAssets($page, $am)
public function registerAssets($view)
{
$am = $view->getAssetManager();
foreach ($this->depends as $name) {
$page->registerAssetBundle($name);
$view->registerAssetBundle($name);
}
if ($this->sourcePath !== null) {
......@@ -128,23 +142,23 @@ class AssetBundle extends Object
$js = is_string($options) ? $options : $js;
if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
if (isset($this->basePath, $this->baseUrl)) {
$js = $converter->convert(ltrim($js, '/'), $this->basePath, $this->baseUrl);
$js = $converter->convert($js, $this->basePath, $this->baseUrl);
} else {
throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.');
}
}
$page->registerJsFile($js, is_array($options) ? $options : array());
$view->registerJsFile($js, is_array($options) ? $options : array());
}
foreach ($this->css as $css => $options) {
$css = is_string($options) ? $options : $css;
if (strpos($css, '//') !== 0 && strpos($css, '://') === false) {
if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
if (isset($this->basePath, $this->baseUrl)) {
$css = $converter->convert(ltrim($css, '/'), $this->basePath, $this->baseUrl);
$css = $converter->convert($css, $this->basePath, $this->baseUrl);
} else {
throw new InvalidConfigException('Both of the "baseUrl" and "basePath" properties must be set.');
}
}
$page->registerCssFile($css, is_array($options) ? $options : array());
$view->registerCssFile($css, is_array($options) ? $options : array());
}
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ use yii\base\InvalidParamException;
use yii\helpers\FileHelper;
/**
* AssetManager manages asset bundles and asset publishing.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
......@@ -135,7 +136,8 @@ class AssetManager extends Component
private $_converter;
/**
* @return IAssetConverter
* Returns the asset converter.
* @return IAssetConverter the asset converter.
*/
public function getConverter()
{
......@@ -149,6 +151,12 @@ class AssetManager extends Component
return $this->_converter;
}
/**
* Sets the asset converter.
* @param array|IAssetConverter $value the asset converter. This can be either
* an object implementing the [[IAssetConverter]] interface, or a configuration
* array that can be used to create the asset converter object.
*/
public function setConverter($value)
{
$this->_converter = $value;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment