The asset bundles

We already mentioned and used asset bundles in brief back in Chapter 3, Automatically Generating the CRUD Code. Let's discuss them in more detail.

The purpose of an asset bundle is to have a group of related CSS and/or JavaScript files lying somewhere in the code base, and to be able to register them in the HTML page with a single PHP call. More than this, asset bundles can depend on other asset bundles, and in this case, a single call to register one top-level "master" asset bundle can result in registering the whole UI for the application. The CSS and JavaScript files are called "assets" here, hence the name of the concept is asset bundle.

An asset bundle with files from an arbitrary folder

The following is a typical asset bundle, which references files from an arbitrary folder in the code base:

class YiiAsset extends AssetBundle
{
    public $sourcePath = '@yii/assets';
    public $js = [
        'yii.js',
    ];
    public $depends = [
        'yiiwebJqueryAsset',
    ];
}

It's a built-in asset bundle, which is required for any built-in widget of Yii 2 to work. It extends the AssetBundle class, and has the following properties defined:

Property name

Meaning

sourcePath

It gives the path alias to the folder, which is prepended to all the paths in JS and the CSS properties in the bundle. This is effectively the only arbitrary folder in the code base containing all of the assets we are going to register.

js

This is an array of relative paths to the files that will be registered as the <script src=""></script> elements in HTML. They are presumably the JavaScript files.

css

This is an array of relative paths to the files that will be registered as the <link rel="stylesheet" href="" /> elements in the resulting HTML file. These files are presumably the CSS files. YiiAsset does not define anything in this property, that is, it does not contain any CSS files.

depends

This is an array of fully qualified names of classes that should be treated as asset bundles and registered before this asset bundle.

This is, of course, not the complete list of properties. A complete description of the AssetBundle class can be found in the Yii 2 source code, in the yii2/web/AssetBundle.php file.

What does it mean to "register" a few CSS or JavaScript files? It means to put the corresponding HTML element into the resulting page and fill its href or src attribute. However, if we store the asset files in some arbitrary folder in the code base, maybe in a place inaccessible by the web server, what URLs do we use?

Asset publishing

To answer the questions just asked, Yii has the AssetManager class and the concept of asset publishing. When you use AssetBundle with the sourcePath property defined, the Yii application utilizes the AssetManager component to copy the directory specified by sourcePath to some directory specified in the AssetManager.basePath parameter. By default, AssetManager.basePath is equal to @webroot/assets, and so in effect, the AssetBundle directory will be copied to some web-accessible location. It will be renamed to a unique, timestamp-dependent hash so that AssetManager will not unnecessarily republish the AssetBundle folder if it has not changed any content at all.

Asset publishing is a very complex part of the Yii framework and, surely, a very important and useful feature. You can get into details of it by reading the official documentation for Yii 2. We will now concentrate on the practical consequences of publishing assets, which are as follows:

  • By default, all the assets are being published in the assets subfolder of your web root. Usually, you have no reason to change this default setting.
  • You can completely erase the contents of this folder, which holds the assets, at any time without any problem except at the time of copying assets again.
  • You must not delete this folder because Yii never checks for its existence and doesn't create it.
  • The whole AssetBundle.sourcePath folder is copied to assets. This means that you can have, say, CSS files in the css subfolder that reference the PNG images stored in the img subfolder by using relative paths such as ../img/<imagename>. This is the same case for fonts.
  • When developing an application, it's meaningless to change files in the @webroot/assets directory. You have to change the original files in AssetBundle.sourcePath and then republish the files again. Yii 2 tries to decide whether it needs to republish assets by comparing timestamps, but it can fail. So a foolproof way to republish assets is just to purge the assets directory so that Yii will be forced to repopulate it.
  • There is this configuration setting called AssetManager.linkAssets, which you can set in the application configuration. When it is true, AssetManager doesn't copy the assets folder but makes a symbolic link for it. This does not work on Windows and may have possible security issues. For example, you have to have FollowSymlinks enabled inside the @webroot/assets folder if you're using Apache. However, on a system where it's not a problem, symlinking assets instead of copying them removes the problem described previously, as @webroot/assets will always have the newest "copy" of the CSS and JavaScript files.

You can publish any folder or file from the code base by making the following call:

list($dir, $url) = Yii:$app->assetManager->publish($path)
  • The $path variable is the path to the file or directory inside the code base. It is processed by Yii::getAlias(), so it can be any path alias understandable by Yii 2.
  • The $dir variable will hold the complete absolute path, given by the $path variable just published.
  • The $url variable will hold the complete absolute URL to the path ($path) just published, so you'll be able to use this URL in your HTML file and be sure that the browser's request to it will really return the published file or directory.

An asset bundle with files from a web-accessible folder

Here is the example AssetBundle definition, which references files that already lie in the web-accessible folder:

class MyUiAsset extends yiiwebAssetBundle
{
    public $basePath = '@webroot/ui';
    public $baseUrl = '@web/ui';
    public $css = ['main.css'];
    public $js = ['main.js'];
}

Dependencies were omitted for brevity.

Here, we see the basePath and baseUrl properties. The base path is the path alias to the folder that contains the files relevant to this bundle. The base URL is the absolute URL prefix that should be appended to all the file references so that the browser will be able to properly request it. Note that the alias system can also be applied to URLs.

Usually, the base URL and base path are similar as we utilize the ability of a web server to serve files directly using the path in any filesystem. However, in more complex cases, you may have complicated URL rewriting in place or maybe routes that dynamically generate CSS or JavaScript code on the fly. All of these are pretty rare, of course, but Yii provides the capabilities anyway.

The point is that when you use the sourcePath property in your asset bundle, Yii first publishes this source folder and modifies the basePath and baseUrl properties of the asset bundle in question for you. As a side effect, it's pointless to use all three properties at the same time as sourcePath will have precedence anyway.

This usage is meaningful when you can tolerate the folders that pre-exist in your web root directory. However, the whole reason to use the asset bundle is to contain the assets relevant to a single library inside a single folder. If you use a lot of asset bundles based on the base path, you will inevitably fill your web root with a lot of different folders from different libraries. And this can become really ugly really fast.

However, nothing will stop you from using the same base path for all the asset bundles (for example @webroot) and just referencing different files in each bundle. This will lead to a tangled mess of interrelated files and is a maintenance nightmare waiting to happen.

Thus, asset bundles based on the source path, in spite of the cumbersome change-republish cycle, look like a more maintainable and idiomatic solution.

Registering CSS and JavaScript files manually

Given the system of asset bundles, you probably will never really need to register assets manually, but in case you do, here's a list of methods you can use for it:

Method call

Effect

registerCss($css, $options)

It will place the <style> HTML element inside the <head> element with the value of $css as the content and the attributes listed in the optional $options array. Ultimately, it makes the Html::style($css, $options) call.

registerCssFile($url, $depends, $options)

It will place a <link> element pointing to $url inside the <head> element of the resulting HTML file. If the optional $depends attribute(s) are defined, then the additional AssetBundle is registered with just $url as the sole CSS element and the dependencies listed in $depends. The optional $options attribute is an array of attributes for the <link> element. Ultimately, it makes the Html::cssFile($url, $options) call.

registerJs($js, $position)

It will place the <script> HTML element as specified by the $position attribute with the value of $js as the content. Ultimately, it makes the Html::script($js, ['type' => 'text/javascript']) call.

registerJsFile($url, $depends, $options)

It will place a <script> HTML element as specified by the position that is determined by the $options['position'] field this time. If the $depends attributes are specified, then the additional asset bundle is registered with just the value specified by the $url attribute as the sole JS element and the dependencies listed in $depends. Ultimately, this method makes the Html::jsFile($url, $options) call (the 'position' key being removed from $options).

Tip

Do note that the CSS and JavaScript files are still better than writing the <script> and <link> elements in HTML layouts or, worse, view files directly. One of the most obvious reasons why they are better is the ability to include only those assets that are relevant to the route currently being processed (that is, to the page being currently rendered).

All these methods are in the View class, so you can use them as $this->registerCss($css) inside the view files and as Yii::$app->view->registerCss($css) anywhere else.

Note that we did not list exact function declarations, only the parts most useful in practice.

For JavaScript files, there's the concept of positions. Here's a list of the possible positions for JavaScript files:

  • View::POS_HEAD places the JavaScript files inside the <head> element
  • View::POS_BEGIN places the JavaScript files at the beginning of the <body> element
  • View::POS_END places the JavaScript files at the end of the <body> element
  • View::POS_READY means the JavaScript code block will be enclosed within jQuery(document).ready() and placed at end of the <body> element
  • View:: POS_LOAD means the JavaScript code block will be enclosed within jQuery(window).load() and placed at the end of the <body> element

    Tip

    We discard the possibility that you need to include <script> somewhere inside the HTML page. Yii will not help you with this misbehavior.

Placing JavaScript in different positions in the asset bundles

There's a really useful trick with asset bundles related to manually registering the JavaScript and CSS files described in the previous section.

By default, when registering an asset bundle, all JavaScript files mentioned in it will be placed at the bottom of the body element. However, there's a method to change this position. Unfortunately, you cannot specify the position for each individual JavaScript file, only for the whole asset bundle.

There's a property called jsOptions in the AssetBundle class. It holds the parameters that will be passed to the registerJsFile call as the $options argument when each of the JavaScript files in the asset bundle will be registered. So you can add the following line to your AssetBundle definition:

public $jsOptions = ['position' => View::POS_HEAD];

All JavaScript files mentioned in the js property will be placed in the <head> element of the resulting HTML page instead of at the end of <body>.

There is also the cssOptions property that can be used to set options for the registerCssFile calls for CSS files. Using these options, you can set the media property of the resulting <link> tag, for example:

public $cssOptions = ['media' => 'print,aural,tty'];

Making a custom asset bundle for our application

Let's create our own asset bundle so that we can initialize our styles and client-side behavior with just a single call.

Create a directory called assets in the root of the code base. Inside it, create a file called ApplicationUiAssetBundle.php with the following definition:

namespace appassets;

use yiiwebAssetBundle;

class ApplicationUiAssetBundle extends AssetBundle
{
    public $sourcePath = '@app/assets/ui';
    public $css = [
        'css/main.css'
    ];
    public $js = [
        'js/main.js'
    ];
    public $depends = [
        'yiiootstrapBootstrapAsset',
        'yiiwebYiiAsset'
    ];
}

Hopefully, this definition completely makes sense. For it to not be a lie, we need to create two asset files in the code base root, which will be empty for now: assets/ui/css/main.css and assets/ui/js/main.js.

As we declared that our assets depend on YiiAsset and BootstrapAsset, we can replace the option of registering them with registering our asset bundle. Inside the main layout file, consider the following line:

yiiootstrapBootstrapAsset::register($this);
yiiwebYiiAsset::register($this);

We can replace these lines with the following:

appassetsApplicationUiAssetBundle::register($this);

Now we have the foundation to write custom styles for our example application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.32.1