Theming is a systematic way of customizing the outlook of pages in a Web application. By applying a new theme, the overall appearance of a Web application can be changed instantly and dramatically.
In Yii, each theme is represented as a directory consisting of view files,
layout files, and relevant resource files such as images, CSS files,
JavaScript files, etc. The name of a theme is its directory name. All
themes reside under the same directory WebRoot/themes
. At any time, only
one theme can be active.
Tip: The default theme root directory
WebRoot/themes
can be configured to be a different one. Simply configure the basePath and the baseUrl properties of the themeManager application component to be the desired ones.
To activate a theme, set the theme property of the Web application to be the name of the desired theme. This can be done either in the application configuration or during runtime in controller actions.
Note: Theme name is case-sensitive. If you attempt to activate a theme that does not exist,
Yii::app()->theme
will returnnull
.
Contents under a theme directory should be organized in the same way as
those under the application base
path. For example, all view files
must be located under views
, layout view files under views/layouts
, and
system view files under views/system
. For example, if we want to replace
the create
view of PostController
with a view in the classic
theme,
we should save the new view file as WebRoot/themes/classic/views/post/create.php
.
For views belonging to controllers in a module,
the corresponding themed view files should also be placed under the views
directory. For example, if the aforementioned PostController
is in a module
named forum
, we should save the create
view file as WebRoot/themes/classic/views/forum/post/create.php
. If the forum
module
is nested in another module named support
, then the view file should be
WebRoot/themes/classic/views/support/forum/post/create.php
.
Note: Because the
views
directory may contain security-sensitive data, it should be configured to prevent from being accessed by Web users.
When we call render or renderPartial to display a view, the corresponding view file as well as the layout file will be looked for in the currently active theme. And if found, those files will be rendered. Otherwise, it falls back to the default location as specified by viewPath and layoutPath.
Tip: Inside a theme view, we often need to link other theme resource files. For example, we may want to show an image file under the theme's
images
directory. Using the baseUrl property of the currently active theme, we can generate the URL for the image as follows,Yii::app()->theme->baseUrl . '/images/FileName.gif'
Below is an example of directory organization for an application with two themes basic
and fancy
.
WebRoot/ assets protected/ .htaccess components/ controllers/ models/ views/ layouts/ main.php site/ index.php themes/ basic/ views/ .htaccess layouts/ main.php site/ index.php fancy/ views/ .htaccess layouts/ main.php site/ index.php
In the application configuration, if we configure
return array(
'theme'=>'basic',
......
);
then the basic
theme will be in effect, which means the application's layout will use
the one under the directory themes/basic/views/layouts
, and the site's index view will
use the one under themes/basic/views/site
. In case a view file is not found in the theme,
it will fall back to the one under the protected/views
directory.
Starting from version 1.1.5, views used by a widget can also be themed. In particular, when we call CWidget::render() to render a widget view, Yii will attempt to search under the theme folder as well as the widget view folder for the desired view file.
To theme the view xyz
for a widget whose class name is Foo
, we should first create a folder named Foo
(same as the widget class name) under the currently active theme's view folder. If the widget class is namespaced (available in PHP 5.3.0 or above), such as \app\widgets\Foo
, we should create a folder named app_widgets_Foo
. That is, we replace the namespace separators with the underscore characters.
We then create a view file named xyz.php
under the newly created folder. To this end, we should have a file themes/basic/views/Foo/xyz.php
, which will be used by the widget to replace its original view, if the currently active theme is basic
.
Note: this feature has been available since version 1.1.3.
When using a widget provided by third party or Yii, we often need to customize it for specific needs. For example, we may want to change the value of CLinkPager::maxButtonCount from 10 (default) to 5. We can accomplish this by passing the initial property values when calling CBaseController::widget to create a widget. However, it becomes troublesome to do so if we have to repeat the same customization in every place we use CLinkPager.
$this->widget('CLinkPager', array(
'pages'=>$pagination,
'maxButtonCount'=>5,
'cssFile'=>false,
));
Using the global widget customization feature, we only need to specify these initial values in a single place, i.e., the application configuration. This makes the customization of widgets more manageable.
To use the global widget customization feature, we need to configure the widgetFactory as follows:
return array(
'components'=>array(
'widgetFactory'=>array(
'widgets'=>array(
'CLinkPager'=>array(
'maxButtonCount'=>5,
'cssFile'=>false,
),
'CJuiDatePicker'=>array(
'language'=>'ru',
),
),
),
),
);
In the above, we specify the global widget customization for both CLinkPager and CJuiDatePicker widgets by configuring the CWidgetFactory::widgets property. Note that the global customization for each widget is represented as a key-value pair in the array, where the key refers to the wiget class name while the value specifies the initial property value array.
Now, whenever we create a CLinkPager widget in a view, the above property values will be assigned to the widget, and we only need to write the following code in the view to create the widget:
$this->widget('CLinkPager', array(
'pages'=>$pagination,
));
We can still override the initial property values when necessary. For example,
if in some view we want to set maxButtonCount
to be 2, we can do the following:
$this->widget('CLinkPager', array(
'pages'=>$pagination,
'maxButtonCount'=>2,
));
While using a theme we can quickly change the outlook of views, we can use skins to systematically customize the outlook of the widgets used in the views.
A skin is an array of name-value pairs that can be used to initialize the properties of a widget. A skin belongs to a widget class, and a widget class can have multiple skins identified by their names. For example, we can have a skin for the CLinkPager widget and the skin is named as classic
.
In order to use the skin feature, we first need to modify the application configuration by configuring the CWidgetFactory::enableSkin property to be true for the widgetFactory
application component:
return array(
'components'=>array(
'widgetFactory'=>array(
'enableSkin'=>true,
),
),
);
Please note that in versions prior to 1.1.3, you need to use the following configuration to enable widget skinning:
return array(
'components'=>array(
'widgetFactory'=>array(
'class'=>'CWidgetFactory',
),
),
);
We then create the needed skins. Skins belonging to the same widget class are stored in a single PHP script file whose name is the widget class name. All these skin files are stored under protected/views/skins
, by default. If you want to change this to be a different directory, you may configure the skinPath
property of the widgetFactory
component. As an example, we may create under protected/views/skins
a file named CLinkPager.php
whose content is as follows,
return array( 'default'=>array( 'nextPageLabel'=>'next', 'prevPageLabel'=>'prev', ), 'classic'=>array( 'header'=>'', 'maxButtonCount'=>5, ), );
In the above, we create two skins for the CLinkPager widget: default
and classic
. The former is the skin that will be applied to any CLinkPager widget that we do not explicitly specify its skin
property, while the latter is the skin to be applied to a CLinkPager widget whose skin
property is specified as classic
. For example, in the following view code, the first pager will use the default
skin while the second the classic
skin:
$this->widget('CLinkPager'); $this->widget('CLinkPager', array('skin'=>'classic'));
If we create a widget with a set of initial property values, they will take precedence and be merged with any applicable skin. For example, the following view code will create a pager whose initial values will be array('header'=>'', 'maxButtonCount'=>6, 'cssFile'=>false)
, which is the result of merging the initial property values specified in the view and the classic
skin.
$this->widget('CLinkPager', array( 'skin'=>'classic', 'maxButtonCount'=>6, 'cssFile'=>false, ));
Note that the skin feature does NOT require using themes. However, when a theme is active, Yii will also look for skins under the skins
directory of the theme's view directory (e.g. WebRoot/themes/classic/views/skins
). In case a skin with the same name exists in both the theme and the main application view directories, the theme skin will take precedence.
If a widget is using a skin that does not exist, Yii will still create the widget as usual without any error.
Info: Using skin may degrade the performance because Yii needs to look for the skin file the first time a widget is being created.
Skin is very similar to the global widget customization feature. The main differences are as follows.
Found a typo or you think this page needs improvement?
Edit it on github !
Override a layout in your theme
If your want to override a layout in your theme without hardcoding the theme layout path in your controller (which won't work if you modify your theme), you can add a beforeAction() method in the Controller component.
Using this piece of code, you will be able to keep gii generated code with $layout starting with '//layouts/'.
/** * Manage theme layout is overriden. * If given layout is overriden, use it instead of protected folder one. Else, use protected folder one. * @see CController::beforeAction() */ public function beforeAction($action) { // If application is using a theme, replace default layout controller variable that start with '//layouts/' with a theme link if(empty(Yii::app()->theme->name) == false && isset($this->layout) == true && strpos($this->layout, '//layouts/') === 0) { // Replace path with slash by dot. $sThemeLayout = 'webroot.themes.'.Yii::app()->theme->name.'.views.layouts.'.str_replace('/', '.', substr($this->layout,10)); // If theme override given layout, get it from theme if($this->getLayoutFile($sThemeLayout) !== false) $this->layout = $sThemeLayout; } return true; }
Take care of automated loaded js files...
When using a specific theme, sometimes we load specific js files.
If your are a newbie in Yii theming, don't forget to have a look at
Yii::app()->getClientScript();
and related automated insertion when you are using Zii widgets for example.
A common issue (i experimented it with my first yii project) is to create your own main.php layout and insert js file such as jquery in your
section.If you do it old fashion way you probably do
<script type="text/javascript" src="<?php echo Yii::app()->theme->baseUrl; ?>/js/jqueryFileThatIDownloadedMyseilfAndPutIntoMyThemeJsFolder.min.js"></script>
It works great but if you insert a zii widget such aszii.widgets.grid.CGridView, you will create a conflict by inserting twice jquery lib and reset some required object. Js error such as '$.param.querystring is not a function' could be painful...
So don't forget to get into Yii getClientScript() by looking at existing theme examples or app examples.
Override widget css globally
If you want to configure some widget css in you main application config file, you probably find that Yii::app()->baseUrl is not accessible yet. So you might use something like this in such case:
'components'=>array( 'widgetFactory'=>array( 'widgets'=>array( 'CLinkPager'=>array( 'cssFile'=>(strlen(dirname($_SERVER['SCRIPT_NAME']))>1 ? dirname($_SERVER['SCRIPT_NAME']) : '' ) . '/css/pager.css', ), ), ), ),
Signup or Login in order to comment.